mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
build: reorganize and reconfigure modules to resolve dependency resolution errors
Gradle, for some reason, does not the path of modules to identify them uniquely. Instead, the 'build id' is used to do so, which consists of the maven group and artifact ID. So, even if two modules are located in separate directories, but have the name group and artifact ID, including both of them in other modules may result in dependency resolution errors.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,35 @@
|
||||
import com.itsaky.androidide.build.config.BuildConfig
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
groupConfig {
|
||||
groupIdSuffix.set("java")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "${BuildConfig.packageName}.javac.services"
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.composite.javac)
|
||||
|
||||
implementation(libs.common.kotlin)
|
||||
implementation(libs.common.utilcode)
|
||||
implementation(libs.google.guava)
|
||||
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.logging.logger)
|
||||
|
||||
testImplementation(libs.tests.junit)
|
||||
testImplementation(libs.tests.google.truth)
|
||||
testImplementation(libs.tests.robolectric)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ This file is part of AndroidIDE.
|
||||
~
|
||||
~ AndroidIDE is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ AndroidIDE is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<manifest/>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.util.Abort;
|
||||
|
||||
/**
|
||||
* @author Tomas Zezula
|
||||
*/
|
||||
public final class CancelAbort extends Abort {
|
||||
CancelAbort() {}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author Tomas Zezula
|
||||
*/
|
||||
public class CancelService {
|
||||
|
||||
/** The context key for the parameter name resolver. */
|
||||
protected static final Context.Key<CancelService> cancelServiceKey = new Context.Key<>();
|
||||
|
||||
protected CancelService() {}
|
||||
|
||||
public static CancelService instance(Context context) {
|
||||
CancelService instance = context.get(cancelServiceKey);
|
||||
if (instance == null) {
|
||||
instance = new CancelService();
|
||||
context.put(cancelServiceKey, instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void preRegister(Context context, CancelService impl) {
|
||||
context.put(CancelService.cancelServiceKey, impl);
|
||||
}
|
||||
|
||||
public final void abortIfCanceled() {
|
||||
if (isCanceled()) {
|
||||
throw new CancelAbort();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCanceled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import openjdk.tools.javac.code.Type;
|
||||
import openjdk.tools.javac.comp.Attr;
|
||||
import openjdk.tools.javac.comp.AttrContext;
|
||||
import openjdk.tools.javac.comp.Env;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.JCTree.JCBlock;
|
||||
import openjdk.tools.javac.tree.JCTree.JCCatch;
|
||||
import openjdk.tools.javac.tree.JCTree.JCClassDecl;
|
||||
import openjdk.tools.javac.tree.JCTree.JCExpression;
|
||||
import openjdk.tools.javac.tree.JCTree.JCMethodDecl;
|
||||
import openjdk.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import openjdk.tools.javac.tree.TreeMaker;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.List;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBAttr extends Attr {
|
||||
|
||||
private final CancelService cancelService;
|
||||
private final TreeMaker tm;
|
||||
private boolean fullyAttribute;
|
||||
private Env<AttrContext> fullyAttributeResult;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(attrKey, (Context.Factory<Attr>) NBAttr::new);
|
||||
}
|
||||
|
||||
public NBAttr(Context context) {
|
||||
super(context);
|
||||
cancelService = CancelService.instance(context);
|
||||
tm = TreeMaker.instance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCatch(JCCatch that) {
|
||||
super.visitBlock(tm.Block(0, List.of(that.param, that.body)));
|
||||
}
|
||||
|
||||
public Env<AttrContext> attributeAndCapture(JCTree tree, Env<AttrContext> env, JCTree to) {
|
||||
try {
|
||||
fullyAttribute = true;
|
||||
|
||||
Env<AttrContext> result =
|
||||
tree instanceof JCExpression
|
||||
? attribExprToTree((JCExpression) tree, env, to)
|
||||
: attribStatToTree(tree, env, to);
|
||||
|
||||
return fullyAttributeResult != null ? fullyAttributeResult : result;
|
||||
} finally {
|
||||
fullyAttribute = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void breakTreeFound(Env<AttrContext> env) {
|
||||
if (fullyAttribute) {
|
||||
fullyAttributeResult = env;
|
||||
} else {
|
||||
try {
|
||||
MethodHandles.lookup()
|
||||
.findSpecial(
|
||||
Attr.class,
|
||||
"breakTreeFound",
|
||||
MethodType.methodType(void.class, Env.class),
|
||||
NBAttr.class)
|
||||
.invokeExact(this, env);
|
||||
} catch (Throwable ex) {
|
||||
sneakyThrows(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Throwable> void sneakyThrows(Throwable t) throws T {
|
||||
throw (T) t;
|
||||
}
|
||||
|
||||
protected void breakTreeFound(Env<AttrContext> env, Type result) {
|
||||
if (fullyAttribute) {
|
||||
fullyAttributeResult = env;
|
||||
} else {
|
||||
try {
|
||||
MethodHandles.lookup()
|
||||
.findSpecial(
|
||||
Attr.class,
|
||||
"breakTreeFound",
|
||||
MethodType.methodType(void.class, Env.class, Type.class),
|
||||
NBAttr.class)
|
||||
.invokeExact(this, env, result);
|
||||
} catch (Throwable ex) {
|
||||
sneakyThrows(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type attribType(JCTree tree, Env<AttrContext> env) {
|
||||
cancelService.abortIfCanceled();
|
||||
return super.attribType(tree, env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClassDef(JCClassDecl tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitClassDef(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodDef(JCMethodDecl tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitMethodDef(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarDef(JCVariableDecl tree) {
|
||||
// for erroneous "var", make sure the synthetic make.Error() has an invalid/synthetic position:
|
||||
tm.at(-1);
|
||||
super.visitVarDef(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBlock(JCBlock tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitBlock(tree);
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.tools.javac.code.ClassFinder;
|
||||
import openjdk.tools.javac.code.Flags;
|
||||
import openjdk.tools.javac.code.Kinds.Kind;
|
||||
import openjdk.tools.javac.code.Symbol;
|
||||
import openjdk.tools.javac.code.Symbol.Completer;
|
||||
import openjdk.tools.javac.code.Symbol.CompletionFailure;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.JCDiagnostic;
|
||||
import openjdk.tools.javac.util.JCDiagnostic.DiagnosticInfo;
|
||||
import openjdk.tools.javac.util.JCDiagnostic.DiagnosticType;
|
||||
import openjdk.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
import openjdk.tools.javac.util.Names;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBClassFinder extends ClassFinder {
|
||||
|
||||
private final Context context;
|
||||
private final Names names;
|
||||
private final JCDiagnostic.Factory diagFactory;
|
||||
private final Log log;
|
||||
private Completer completer;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(classFinderKey, (Context.Factory<ClassFinder>) NBClassFinder::new);
|
||||
}
|
||||
|
||||
public NBClassFinder(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.names = Names.instance(context);
|
||||
this.diagFactory = JCDiagnostic.Factory.instance(context);
|
||||
this.log = Log.instance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Completer getCompleter() {
|
||||
if (completer == null) {
|
||||
try {
|
||||
Class.forName("openjdk.tools.javac.model.LazyTreeLoader");
|
||||
// patched nb-javac, handles missing java.lang itself:
|
||||
completer = super.getCompleter();
|
||||
} catch (ClassNotFoundException e) {
|
||||
Completer delegate = super.getCompleter();
|
||||
completer =
|
||||
sym -> {
|
||||
delegate.complete(sym);
|
||||
if (sym.kind == Kind.PCK
|
||||
&& sym.flatName() == names.java_lang
|
||||
&& sym.members().isEmpty()) {
|
||||
sym.flags_field |= Flags.EXISTS;
|
||||
try {
|
||||
Class<?> dcfhClass =
|
||||
Class.forName("openjdk.tools.javac.code.DeferredCompletionFailureHandler");
|
||||
Constructor<CompletionFailure> constr =
|
||||
CompletionFailure.class.getDeclaredConstructor(
|
||||
Symbol.class, Supplier.class, dcfhClass);
|
||||
Object dcfh =
|
||||
dcfhClass.getDeclaredMethod("instance", Context.class).invoke(null, context);
|
||||
throw constr.newInstance(
|
||||
sym,
|
||||
(Supplier<JCDiagnostic>)
|
||||
() ->
|
||||
diagFactory.create(
|
||||
log.currentSource(),
|
||||
new SimpleDiagnosticPosition(0),
|
||||
DiagnosticInfo.of(
|
||||
DiagnosticType.ERROR,
|
||||
"compiler",
|
||||
"cant.resolve",
|
||||
"package",
|
||||
"java.lang")),
|
||||
dcfh);
|
||||
} catch (ClassNotFoundException
|
||||
| NoSuchMethodException
|
||||
| SecurityException
|
||||
| IllegalAccessException
|
||||
| IllegalArgumentException
|
||||
| InvocationTargetException
|
||||
| InstantiationException ex) {
|
||||
Logger.getLogger(NBClassFinder.class.getName()).log(Level.FINE, null, ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return completer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JavaFileObject preferredFileObject(JavaFileObject a, JavaFileObject b) {
|
||||
if (b.getName().toLowerCase(Locale.ROOT).endsWith(".sig")) {
|
||||
// do not prefer sources over sig files (unless sources are newer):
|
||||
boolean prevPreferSource = preferSource;
|
||||
try {
|
||||
preferSource = false;
|
||||
return super.preferredFileObject(a, b);
|
||||
} finally {
|
||||
preferSource = prevPreferSource;
|
||||
}
|
||||
}
|
||||
return super.preferredFileObject(a, b);
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import static openjdk.tools.javac.jvm.ClassFile.Version.V45_3;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jdkx.tools.ForwardingJavaFileObject;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.tools.javac.code.ClassFinder.BadClassFile;
|
||||
import openjdk.tools.javac.code.Symbol;
|
||||
import openjdk.tools.javac.code.Symbol.ClassSymbol;
|
||||
import openjdk.tools.javac.jvm.ClassFile;
|
||||
import openjdk.tools.javac.jvm.ClassFile.Version;
|
||||
import openjdk.tools.javac.jvm.ClassReader;
|
||||
import openjdk.tools.javac.resources.CompilerProperties.Warnings;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
import openjdk.tools.javac.util.Name;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBClassReader extends ClassReader {
|
||||
|
||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(NBClassReader.class);
|
||||
private final NBNames nbNames;
|
||||
private final Log log;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(classReaderKey, (Context.Factory<ClassReader>) NBClassReader::new);
|
||||
}
|
||||
|
||||
public NBClassReader(Context context) {
|
||||
super(context);
|
||||
|
||||
nbNames = NBNames.instance(context);
|
||||
log = Log.instance(context);
|
||||
|
||||
NBAttributeReader[] readers = {
|
||||
new NBAttributeReader(
|
||||
nbNames._org_netbeans_EnclosingMethod, V45_3, CLASS_OR_MEMBER_ATTRIBUTE) {
|
||||
public void read(Symbol sym, int attrLen) {
|
||||
int newbp = bp + attrLen;
|
||||
readEnclosingMethodAttr(sym);
|
||||
bp = newbp;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
for (NBAttributeReader r : readers) {
|
||||
attributeReaders.put(r.getName(), r);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BadClassFile badClassFile(final String key, final Object... args) {
|
||||
LOG.debug("Bad class file {} {}", key, TextUtils.join(", ", args), new RuntimeException());
|
||||
return super.badClassFile(key, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readClassFile(ClassSymbol c) {
|
||||
try {
|
||||
super.readClassFile(c);
|
||||
} catch (BadClassFile cf) {
|
||||
if ("compiler.misc.bad.class.file.header".equals(cf.getDiagnostic().getCode())) {
|
||||
JavaFileObject origFile = c.classfile;
|
||||
try (InputStream in = origFile.openInputStream()) {
|
||||
byte[] data = readFile(in);
|
||||
int major = (Byte.toUnsignedInt(data[6]) << 8) + Byte.toUnsignedInt(data[7]);
|
||||
int maxMajor = ClassFile.Version.MAX().major;
|
||||
if (maxMajor < major) {
|
||||
if (log.currentSourceFile() != null) {
|
||||
log.warning(0, Warnings.BigMajorVersion(origFile, major, maxMajor));
|
||||
}
|
||||
data[6] = (byte) (maxMajor >> 8);
|
||||
data[7] = (byte) (maxMajor & 0xFF);
|
||||
c.classfile =
|
||||
new ForwardingJavaFileObject<JavaFileObject>(origFile) {
|
||||
@Override
|
||||
public InputStream openInputStream() {
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
};
|
||||
super.readClassFile(c);
|
||||
return;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(NBClassReader.class.getName()).log(Level.FINE, null, ex);
|
||||
} finally {
|
||||
c.classfile = origFile;
|
||||
}
|
||||
}
|
||||
throw cf;
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] readFile(final InputStream in) throws IOException {
|
||||
byte[] data = new byte[Math.max(in.available(), 256)];
|
||||
int off = 0;
|
||||
int read;
|
||||
while ((read = in.read(data, off, data.length - off)) != (-1)) {
|
||||
off += read;
|
||||
if (data.length == off) {
|
||||
data = Arrays.copyOf(data, 2 * (data.length + in.available()));
|
||||
}
|
||||
}
|
||||
return Arrays.copyOf(data, off);
|
||||
}
|
||||
|
||||
private abstract class NBAttributeReader extends AttributeReader {
|
||||
|
||||
private NBAttributeReader(Name name, Version version, Set<AttributeKind> kinds) {
|
||||
super(name, version, kinds);
|
||||
}
|
||||
|
||||
private Name getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.code.Types;
|
||||
import openjdk.tools.javac.jvm.ClassWriter;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBClassWriter extends ClassWriter {
|
||||
|
||||
private final NBNames nbNames;
|
||||
private final Types types;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(classWriterKey, (Context.Factory<ClassWriter>) NBClassWriter::new);
|
||||
}
|
||||
|
||||
protected NBClassWriter(Context context) {
|
||||
super(context);
|
||||
nbNames = NBNames.instance(context);
|
||||
types = Types.instance(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.code.Symbol.TypeSymbol;
|
||||
import openjdk.tools.javac.code.Symtab;
|
||||
import openjdk.tools.javac.comp.AttrContext;
|
||||
import openjdk.tools.javac.comp.Enter;
|
||||
import openjdk.tools.javac.comp.Env;
|
||||
import openjdk.tools.javac.main.JavaCompiler;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.JCTree.JCClassDecl;
|
||||
import openjdk.tools.javac.tree.TreeInfo;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBEnter extends Enter {
|
||||
|
||||
private final CancelService cancelService;
|
||||
private final Symtab syms;
|
||||
private final NBJavaCompiler compiler;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(enterKey, (Context.Factory<Enter>) NBEnter::new);
|
||||
}
|
||||
|
||||
public NBEnter(Context context) {
|
||||
super(context);
|
||||
cancelService = CancelService.instance(context);
|
||||
syms = Symtab.instance(context);
|
||||
JavaCompiler c = JavaCompiler.instance(context);
|
||||
compiler = c instanceof NBJavaCompiler ? (NBJavaCompiler) c : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void doUnenter(JCTree.JCCompilationUnit cu, JCTree tree) {
|
||||
super.unenter(cu, tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Env<AttrContext> getEnv(TypeSymbol sym) {
|
||||
Env<AttrContext> env = super.getEnv(sym);
|
||||
if (compiler != null) {
|
||||
compiler.maybeInvokeDesugarCallback(env);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTopLevel(JCTree.JCCompilationUnit tree) {
|
||||
if (TreeInfo.isModuleInfo(tree) && tree.modle == syms.noModule) {
|
||||
// workaround: when source level == 8, then visitTopLevel crashes for module-info.java
|
||||
return;
|
||||
}
|
||||
super.visitTopLevel(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClassDef(JCClassDecl tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitClassDef(tree);
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
import openjdk.tools.javac.comp.AttrContext;
|
||||
import openjdk.tools.javac.comp.Env;
|
||||
import openjdk.tools.javac.main.JavaCompiler;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.List;
|
||||
import openjdk.tools.javac.util.Pair;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBJavaCompiler extends JavaCompiler {
|
||||
|
||||
private final CancelService cancelService;
|
||||
private Consumer<Env<AttrContext>> desugarCallback;
|
||||
private boolean desugaring;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(compilerKey, (Context.Factory<JavaCompiler>) NBJavaCompiler::new);
|
||||
}
|
||||
|
||||
public NBJavaCompiler(Context context) {
|
||||
super(context);
|
||||
cancelService = CancelService.instance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAnnotations(
|
||||
List<JCTree.JCCompilationUnit> roots, Collection<String> classnames) {
|
||||
if (roots.isEmpty()) {
|
||||
super.processAnnotations(roots, classnames);
|
||||
} else {
|
||||
setOrigin(roots.head.sourcefile.toUri().toString());
|
||||
try {
|
||||
super.processAnnotations(roots, classnames);
|
||||
} finally {
|
||||
setOrigin("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setOrigin(String origin) {
|
||||
fileManager.handleOption("apt-origin", Collections.singletonList(origin).iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void desugar(
|
||||
Env<AttrContext> env, Queue<Pair<Env<AttrContext>, JCTree.JCClassDecl>> results) {
|
||||
boolean prevDesugaring = desugaring;
|
||||
try {
|
||||
desugaring = true;
|
||||
super.desugar(env, results);
|
||||
} finally {
|
||||
desugaring = prevDesugaring;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDesugarCallback(Consumer<Env<AttrContext>> callback) {
|
||||
this.desugarCallback = callback;
|
||||
}
|
||||
|
||||
void maybeInvokeDesugarCallback(Env<AttrContext> env) {
|
||||
if (desugaring && desugarCallback != null) {
|
||||
desugarCallback.accept(env);
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jdkx.lang.model.element.Element;
|
||||
import openjdk.source.tree.VariableTree;
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.tools.javac.api.JavacTrees;
|
||||
import openjdk.tools.javac.code.Flags;
|
||||
import openjdk.tools.javac.code.Symbol;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import openjdk.tools.javac.tree.TreeInfo;
|
||||
import openjdk.tools.javac.tree.TreeMaker;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBJavacTrees extends JavacTrees {
|
||||
|
||||
private final Map<Element, TreePath> element2paths = new HashMap<>();
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(JavacTrees.class, (Context.Factory<JavacTrees>) NBJavacTrees::new);
|
||||
}
|
||||
|
||||
protected NBJavacTrees(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreePath getPath(Element e) {
|
||||
TreePath path = super.getPath(e);
|
||||
return path != null ? path : element2paths.get(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Symbol getElement(TreePath path) {
|
||||
return TreeInfo.symbolFor((JCTree) path.getLeaf());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Copier createCopier(TreeMaker maker) {
|
||||
return new Copier(maker) {
|
||||
@Override
|
||||
public JCTree visitVariable(VariableTree node, JCTree p) {
|
||||
JCVariableDecl old = (JCVariableDecl) node;
|
||||
JCVariableDecl nue = (JCVariableDecl) super.visitVariable(node, p);
|
||||
if (old.sym != null) {
|
||||
nue.mods.flags |= old.sym.flags_field & Flags.EFFECTIVELY_FINAL;
|
||||
}
|
||||
return nue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void addPathForElement(Element elem, TreePath path) {
|
||||
element2paths.put(elem, path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.tools.javac.code.Symbol;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.JCDiagnostic;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
|
||||
/**
|
||||
* @author Tomas Zezula
|
||||
*/
|
||||
public class NBLog extends Log {
|
||||
|
||||
private static final String ERR_NOT_IN_PROFILE = "not.in.profile"; // NOI18N
|
||||
|
||||
private final Map<URI, Collection<Symbol.ClassSymbol>> notInProfiles = new HashMap<>();
|
||||
|
||||
private JavaFileObject partialReparseFile;
|
||||
private final Set<Integer> seenPartialReparsePositions = new HashSet<>();
|
||||
|
||||
protected NBLog(final Context context, final PrintWriter output) {
|
||||
super(context, output);
|
||||
}
|
||||
|
||||
public static NBLog instance(Context context) {
|
||||
final Log log = Log.instance(context);
|
||||
if (!(log instanceof NBLog)) {
|
||||
throw new InternalError("No NBLog instance!"); // NOI18N
|
||||
}
|
||||
return (NBLog) log;
|
||||
}
|
||||
|
||||
public static void preRegister(Context context, final PrintWriter output) {
|
||||
context.put(logKey, (Context.Factory<Log>) c -> new NBLog(c, output));
|
||||
}
|
||||
|
||||
public void startPartialReparse(JavaFileObject inFile) {
|
||||
partialReparseFile = inFile;
|
||||
}
|
||||
|
||||
public void endPartialReparse(JavaFileObject inFile) {
|
||||
partialReparseFile = null;
|
||||
seenPartialReparsePositions.clear(); // TODO: not tested
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultMaxErrors() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultMaxWarnings() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldReport(JavaFileObject file, int pos) {
|
||||
if (partialReparseFile != null) {
|
||||
return file == partialReparseFile && seenPartialReparsePositions.add(pos);
|
||||
} else {
|
||||
return super.shouldReport(file, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(JCDiagnostic diagnostic) {
|
||||
// XXX: needs testing!
|
||||
if (diagnostic.getKind() == Diagnostic.Kind.ERROR
|
||||
&& ERR_NOT_IN_PROFILE.equals(diagnostic.getCode())) {
|
||||
final JavaFileObject currentFile = currentSourceFile();
|
||||
if (currentFile != null) {
|
||||
final URI uri = currentFile.toUri();
|
||||
Symbol.ClassSymbol type = (Symbol.ClassSymbol) diagnostic.getArgs()[0];
|
||||
Collection<Symbol.ClassSymbol> types =
|
||||
notInProfiles.computeIfAbsent(uri, k -> new ArrayList<>());
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
super.report(diagnostic);
|
||||
}
|
||||
|
||||
Collection<? extends Symbol.ClassSymbol> removeNotInProfile(final URI uri) {
|
||||
return uri == null ? null : notInProfiles.remove(uri);
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.tools.javac.api.JavacTrees;
|
||||
import openjdk.tools.javac.comp.MemberEnter;
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import openjdk.tools.javac.tree.JCTree.JCImport;
|
||||
import openjdk.tools.javac.tree.JCTree.JCMethodDecl;
|
||||
import openjdk.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBMemberEnter extends MemberEnter {
|
||||
|
||||
private final CancelService cancelService;
|
||||
private final JavacTrees trees;
|
||||
private final boolean backgroundScan;
|
||||
|
||||
public NBMemberEnter(Context context, boolean backgroundScan) {
|
||||
super(context);
|
||||
cancelService = CancelService.instance(context);
|
||||
trees = JavacTrees.instance(context);
|
||||
this.backgroundScan = backgroundScan;
|
||||
}
|
||||
|
||||
public static void preRegister(Context context, boolean backgroundScan) {
|
||||
context.put(
|
||||
MemberEnter.class,
|
||||
(Context.Factory<MemberEnter>) c -> new NBMemberEnter(c, backgroundScan));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTopLevel(JCCompilationUnit tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitTopLevel(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitImport(JCImport tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitImport(tree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodDef(JCMethodDecl tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitMethodDef(tree);
|
||||
if (!backgroundScan && trees instanceof NBJavacTrees && !env.enclClass.defs.contains(tree)) {
|
||||
TreePath path = trees.getPath(env.toplevel, env.enclClass);
|
||||
if (path != null) {
|
||||
((NBJavacTrees) trees).addPathForElement(tree.sym, new TreePath(path, tree));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarDef(JCVariableDecl tree) {
|
||||
cancelService.abortIfCanceled();
|
||||
super.visitVarDef(tree);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Name;
|
||||
import openjdk.tools.javac.util.Names;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBNames {
|
||||
|
||||
public static final Context.Key<NBNames> nbNamesKey = new Context.Key<>();
|
||||
public final Name _org_netbeans_EnclosingMethod;
|
||||
public final Name _org_netbeans_TypeSignature;
|
||||
public final Name _org_netbeans_ParameterNames;
|
||||
public final Name _org_netbeans_SourceLevelAnnotations;
|
||||
public final Name _org_netbeans_SourceLevelParameterAnnotations;
|
||||
public final Name _org_netbeans_SourceLevelTypeAnnotations;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(nbNamesKey, (Context.Factory<NBNames>) NBNames::new);
|
||||
}
|
||||
|
||||
protected NBNames(Context context) {
|
||||
Names n = Names.instance(context);
|
||||
|
||||
_org_netbeans_EnclosingMethod = n.fromString("org.netbeans.EnclosingMethod");
|
||||
_org_netbeans_TypeSignature = n.fromString("org.netbeans.TypeSignature");
|
||||
_org_netbeans_ParameterNames = n.fromString("org.netbeans.ParameterNames");
|
||||
_org_netbeans_SourceLevelAnnotations = n.fromString("org.netbeans.SourceLevelAnnotations");
|
||||
_org_netbeans_SourceLevelParameterAnnotations =
|
||||
n.fromString("org.netbeans.SourceLevelParameterAnnotations");
|
||||
_org_netbeans_SourceLevelTypeAnnotations =
|
||||
n.fromString("org.netbeans.SourceLevelTypeAnnotations");
|
||||
}
|
||||
|
||||
public static NBNames instance(Context context) {
|
||||
NBNames instance = context.get(nbNamesKey);
|
||||
if (instance == null) instance = new NBNames(context);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.parser.JavacParser;
|
||||
import openjdk.tools.javac.parser.Lexer;
|
||||
import openjdk.tools.javac.parser.ParserFactory;
|
||||
import openjdk.tools.javac.parser.ScannerFactory;
|
||||
import openjdk.tools.javac.parser.Tokens.Comment;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.JCTree.JCClassDecl;
|
||||
import openjdk.tools.javac.tree.JCTree.JCEnhancedForLoop;
|
||||
import openjdk.tools.javac.tree.JCTree.JCExpression;
|
||||
import openjdk.tools.javac.tree.JCTree.JCModifiers;
|
||||
import openjdk.tools.javac.tree.JCTree.JCStatement;
|
||||
import openjdk.tools.javac.tree.JCTree.JCTypeParameter;
|
||||
import openjdk.tools.javac.tree.TreeInfo;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.List;
|
||||
import openjdk.tools.javac.util.Name;
|
||||
import openjdk.tools.javac.util.Names;
|
||||
import openjdk.tools.javac.util.Options;
|
||||
import openjdk.tools.javac.util.Position;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBParserFactory extends ParserFactory {
|
||||
|
||||
public static final String KEEP_COMMENTS_OVERRIDE = "keepCommentsOverride";
|
||||
public static final String KEEP_COMMENTS_OVERRIDE_KEEP = "keep";
|
||||
public static final String KEEP_COMMENTS_OVERRIDE_IGNORE = "ignore";
|
||||
|
||||
protected final ScannerFactory scannerFactory;
|
||||
protected final Names names;
|
||||
protected final CancelService cancelService;
|
||||
|
||||
protected final String keepCommentsOverride;
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(parserFactoryKey, (Context.Factory<ParserFactory>) NBParserFactory::new);
|
||||
}
|
||||
|
||||
protected NBParserFactory(Context context) {
|
||||
super(context);
|
||||
this.scannerFactory = ScannerFactory.instance(context);
|
||||
this.names = Names.instance(context);
|
||||
this.cancelService = CancelService.instance(context);
|
||||
this.keepCommentsOverride = Options.instance(context).get(KEEP_COMMENTS_OVERRIDE);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavacParser newParser(
|
||||
CharSequence input,
|
||||
boolean keepDocComments,
|
||||
boolean keepEndPos,
|
||||
boolean keepLineMap,
|
||||
boolean parseModuleInfo) {
|
||||
var keepDocCommentsOverride = keepDocComments;
|
||||
if (this.keepCommentsOverride != null) {
|
||||
if (KEEP_COMMENTS_OVERRIDE_IGNORE.equals(this.keepCommentsOverride)) {
|
||||
keepDocCommentsOverride = false;
|
||||
} else if (KEEP_COMMENTS_OVERRIDE_KEEP.equals(this.keepCommentsOverride)) {
|
||||
keepDocCommentsOverride = true;
|
||||
}
|
||||
}
|
||||
|
||||
Lexer lexer = scannerFactory.newScanner(input, keepDocCommentsOverride);
|
||||
return new NBJavacParser(
|
||||
this, lexer, keepDocCommentsOverride, keepLineMap, keepEndPos, parseModuleInfo,
|
||||
cancelService);
|
||||
}
|
||||
|
||||
public static class NBJavacParser extends JavacParser {
|
||||
|
||||
private final Names names;
|
||||
private final CancelService cancelService;
|
||||
|
||||
public NBJavacParser(
|
||||
NBParserFactory fac,
|
||||
Lexer S,
|
||||
boolean keepDocComments,
|
||||
boolean keepLineMap,
|
||||
boolean keepEndPos,
|
||||
boolean parseModuleInfo,
|
||||
CancelService cancelService) {
|
||||
super(fac, S, keepDocComments, keepLineMap, keepEndPos, parseModuleInfo);
|
||||
this.names = fac.names;
|
||||
this.cancelService = cancelService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractEndPosTable newEndPosTable(boolean keepEndPositions) {
|
||||
AbstractEndPosTable res = super.newEndPosTable(keepEndPositions);
|
||||
|
||||
if (keepEndPositions) {
|
||||
return new EndPosTableImpl(S, this, (SimpleEndPosTable) res);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndPos(JCTree jctree) {
|
||||
return TreeInfo.getEndPos(jctree, endPosTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JCStatement parseSimpleStatement() {
|
||||
JCStatement result = super.parseSimpleStatement();
|
||||
// workaround: if the code looks like:
|
||||
// for (name : <collection>) {...}
|
||||
// the "name" will be made a type of a variable with name "<error>", with
|
||||
// no end position. Inject the end position for the variable:
|
||||
if (result instanceof JCEnhancedForLoop) {
|
||||
JCEnhancedForLoop tree = (JCEnhancedForLoop) result;
|
||||
if (getEndPos(tree.var) == Position.NOPOS) {
|
||||
endPosTable.storeEnd(tree.var, getEndPos(tree.var.vartype));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JCClassDecl classDeclaration(JCModifiers mods, Comment dc) {
|
||||
if (cancelService != null) {
|
||||
cancelService.abortIfCanceled();
|
||||
}
|
||||
return super.classDeclaration(mods, dc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JCClassDecl interfaceDeclaration(JCModifiers mods, Comment dc) {
|
||||
if (cancelService != null) {
|
||||
cancelService.abortIfCanceled();
|
||||
}
|
||||
return super.interfaceDeclaration(mods, dc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JCClassDecl enumDeclaration(JCModifiers mods, Comment dc) {
|
||||
if (cancelService != null) {
|
||||
cancelService.abortIfCanceled();
|
||||
}
|
||||
return super.enumDeclaration(mods, dc);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JCTree methodDeclaratorRest(
|
||||
int pos,
|
||||
JCModifiers mods,
|
||||
JCExpression type,
|
||||
Name name,
|
||||
List<JCTypeParameter> typarams,
|
||||
boolean isInterface,
|
||||
boolean isVoid,
|
||||
boolean isRecord,
|
||||
Comment dc) {
|
||||
if (cancelService != null) {
|
||||
cancelService.abortIfCanceled();
|
||||
}
|
||||
return super.methodDeclaratorRest(
|
||||
pos, mods, type, name, typarams, isInterface, isVoid, isRecord, dc);
|
||||
}
|
||||
|
||||
public final class EndPosTableImpl extends AbstractEndPosTable {
|
||||
|
||||
private final Lexer lexer;
|
||||
private final SimpleEndPosTable delegate;
|
||||
|
||||
private EndPosTableImpl(Lexer lexer, JavacParser parser, SimpleEndPosTable delegate) {
|
||||
super(parser);
|
||||
this.lexer = lexer;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void resetErrorEndPos() {
|
||||
delegate.errorEndPos = Position.NOPOS;
|
||||
errorEndPos = delegate.errorEndPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndPos(JCTree jctree) {
|
||||
return delegate.getEndPos(jctree);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeEnd(JCTree tree, int endpos) {
|
||||
if (endpos >= 0) {
|
||||
delegate.storeEnd(tree, endpos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int replaceTree(JCTree jctree, JCTree jctree1) {
|
||||
return delegate.replaceTree(jctree, jctree1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends JCTree> T to(T t) {
|
||||
storeEnd(t, parser.token().endPos);
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends JCTree> T toP(T t) {
|
||||
storeEnd(t, lexer.prevToken().endPos);
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorEndPos(int errPos) {
|
||||
delegate.setErrorEndPos(errPos);
|
||||
errorEndPos = delegate.errorEndPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.code.Symbol;
|
||||
import openjdk.tools.javac.code.Symbol.TypeSymbol;
|
||||
import openjdk.tools.javac.code.Type;
|
||||
import openjdk.tools.javac.comp.AttrContext;
|
||||
import openjdk.tools.javac.comp.Env;
|
||||
import openjdk.tools.javac.comp.Resolve;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBResolve extends Resolve {
|
||||
private boolean accessibleOverride;
|
||||
|
||||
public static NBResolve instance(Context context) {
|
||||
Resolve instance = context.get(resolveKey);
|
||||
if (instance == null) instance = new NBResolve(context);
|
||||
return (NBResolve) instance;
|
||||
}
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(resolveKey, (Context.Factory<Resolve>) NBResolve::new);
|
||||
}
|
||||
|
||||
protected NBResolve(Context ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public static boolean isStatic(Env<AttrContext> env) {
|
||||
return Resolve.isStatic(env);
|
||||
}
|
||||
|
||||
public void disableAccessibilityChecks() {
|
||||
accessibleOverride = true;
|
||||
}
|
||||
|
||||
public void restoreAccessbilityChecks() {
|
||||
accessibleOverride = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessible(Env<AttrContext> env, TypeSymbol c, boolean checkInner) {
|
||||
if (accessibleOverride) return true;
|
||||
return super.isAccessible(env, c, checkInner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessible(Env<AttrContext> env, Type site, Symbol sym, boolean checkInner) {
|
||||
if (accessibleOverride) return true;
|
||||
return super.isAccessible(env, site, sym, checkInner);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services;
|
||||
|
||||
import openjdk.tools.javac.code.Symtab;
|
||||
import openjdk.tools.javac.code.Types;
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import openjdk.tools.javac.tree.TreeMaker;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Names;
|
||||
|
||||
/**
|
||||
* @author lahvac
|
||||
*/
|
||||
public class NBTreeMaker extends TreeMaker {
|
||||
|
||||
private final Names names;
|
||||
private final Types types;
|
||||
private final Symtab syms;
|
||||
|
||||
protected NBTreeMaker(JCCompilationUnit toplevel, Names names, Types types, Symtab syms) {
|
||||
super(toplevel, names, types, syms);
|
||||
this.names = names;
|
||||
this.types = types;
|
||||
this.syms = syms;
|
||||
}
|
||||
|
||||
public static void preRegister(Context context) {
|
||||
context.put(treeMakerKey, (Context.Factory<TreeMaker>) NBTreeMaker::new);
|
||||
}
|
||||
|
||||
protected NBTreeMaker(Context context) {
|
||||
super(context);
|
||||
this.names = Names.instance(context);
|
||||
this.types = Types.instance(context);
|
||||
this.syms = Symtab.instance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeMaker forToplevel(JCCompilationUnit toplevel) {
|
||||
return new NBTreeMaker(toplevel, names, types, syms);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.CancelService
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Cancel service implementation for the java compiler.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CancelServiceImpl : CancelService() {
|
||||
val cancelled = AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
* Sets the cancellation flag.
|
||||
*
|
||||
* @return `true` if compilation process was running and it was set to be cancelled, `false`
|
||||
* otherwise.
|
||||
*/
|
||||
fun cancel(): Boolean {
|
||||
ILogger.ROOT.info("...requesting compilation cancellation")
|
||||
return !cancelled.getAndSet(true)
|
||||
}
|
||||
|
||||
override fun isCanceled(): Boolean = cancelled.get()
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.compiler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface JavacFlowListener {
|
||||
|
||||
Context.Key<JavacFlowListener> flowListenerKey = new Context.Key<>();
|
||||
|
||||
static JavacFlowListener instance(@NonNull Context context) {
|
||||
return context.get(flowListenerKey);
|
||||
}
|
||||
|
||||
boolean hasFlowCompleted(final JavaFileObject jfo);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services.compiler
|
||||
|
||||
import openjdk.tools.javac.api.JavacTaskImpl
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class ReusableBorrow
|
||||
internal constructor(
|
||||
private val reusableCompiler: ReusableCompiler,
|
||||
@JvmField val task: JavacTaskImpl
|
||||
) : AutoCloseable {
|
||||
|
||||
private var closed = false
|
||||
|
||||
override fun close() {
|
||||
if (closed) {
|
||||
return
|
||||
}
|
||||
// not returning the context to the pool if task crashes with an exception
|
||||
// the task/context may be in a broken state
|
||||
reusableCompiler.currentContext!!.clear()
|
||||
task.cleanup()
|
||||
reusableCompiler.checkedOut = false
|
||||
closed = true
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Forked from JavacTaskImpl
|
||||
package com.itsaky.androidide.javac.services.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.CancelService
|
||||
import jdkx.tools.DiagnosticListener
|
||||
import jdkx.tools.JavaFileManager
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.tools.javac.api.JavacTaskImpl
|
||||
import openjdk.tools.javac.api.JavacTool
|
||||
|
||||
/**
|
||||
* A pool of reusable JavacTasks. When a task is no valid anymore, it is returned to the pool, and
|
||||
* its Context may be reused for future processing in some cases. The reuse is achieved by replacing
|
||||
* some components (most notably JavaCompiler and Log) with reusable counterparts, and by cleaning
|
||||
* up leftovers from previous compilation.
|
||||
*
|
||||
* For each combination of options, a separate task/context is created and kept, as most option
|
||||
* values are cached inside components themselves.
|
||||
*
|
||||
* When the compilation redefines sensitive classes (e.g. classes in the the java.* packages), the
|
||||
* task/context is not reused.
|
||||
*
|
||||
* When the task is reused, then packages that were already listed won't be listed again.
|
||||
*
|
||||
* Care must be taken to only return tasks that won't be used by the original caller.
|
||||
*
|
||||
* Care must also be taken when custom components are installed, as those are not cleaned when the
|
||||
* task/context is reused, and subsequent getTask may return a task based on a context with these
|
||||
* custom components.
|
||||
*
|
||||
* **This is NOT part of any supported API. If you write code that depends on this, you do so at
|
||||
* your own risk. This code and its internal interfaces are subject to change or deletion without
|
||||
* notice.**
|
||||
*/
|
||||
open class ReusableCompiler {
|
||||
private val systemProvider = JavacTool.create()
|
||||
private val currentOptions = mutableListOf<String>()
|
||||
@JvmField var currentContext: ReusableContext? = null
|
||||
|
||||
internal var checkedOut = false
|
||||
|
||||
/**
|
||||
* Creates a new task as if by [jdkx.tools.JavaCompiler.getTask] and runs the provided worker with
|
||||
* it. The task is only valid while the worker is running. The internal structures may be reused
|
||||
* from some previous compilation.
|
||||
*
|
||||
* @param fileManager a file manager; if `null` use the compiler's standard filemanager
|
||||
* @param diagnosticListener a diagnostic listener; if `null` use the compiler's default method
|
||||
* for reporting diagnostics
|
||||
* @param options compiler options, `null` means no options
|
||||
* @param classes names of classes to be processed by annotation processing, `null` means no class
|
||||
* names
|
||||
* @param compilationUnits the compilation units to compile, `null` means no compilation units
|
||||
* @return an object representing the compilation
|
||||
* @throws RuntimeException if an unrecoverable error occurred in a user supplied component. The
|
||||
* [cause][Throwable.cause] will be the error in user code.
|
||||
* @throws IllegalArgumentException if any of the options are invalid, or if any of the given
|
||||
* compilation units are of other kind than [source][JavaFileObject.Kind.SOURCE]
|
||||
*/
|
||||
open fun getTask(
|
||||
fileManager: JavaFileManager?,
|
||||
diagnosticListener: DiagnosticListener<in JavaFileObject?>?,
|
||||
options: Iterable<String>,
|
||||
classes: Iterable<String>,
|
||||
compilationUnits: Iterable<JavaFileObject?>?
|
||||
): ReusableBorrow {
|
||||
|
||||
if (checkedOut) {
|
||||
throw RuntimeException("Compiler is already in-use!")
|
||||
}
|
||||
|
||||
checkedOut = true
|
||||
val opts = options.toList()
|
||||
if (opts != currentOptions) {
|
||||
currentOptions.clear()
|
||||
currentOptions.addAll(opts)
|
||||
currentContext = onCreateContext()
|
||||
}
|
||||
|
||||
val task =
|
||||
systemProvider.getTask(
|
||||
null,
|
||||
fileManager,
|
||||
diagnosticListener,
|
||||
opts,
|
||||
classes,
|
||||
compilationUnits,
|
||||
currentContext
|
||||
) as JavacTaskImpl
|
||||
|
||||
task.addTaskListener(currentContext)
|
||||
|
||||
return onCreateBorrow(task)
|
||||
}
|
||||
|
||||
protected open fun onCreateContext(): ReusableContext {
|
||||
return ReusableContext(cancelService)
|
||||
}
|
||||
|
||||
protected open fun onCreateBorrow(task: JavacTaskImpl): ReusableBorrow {
|
||||
return ReusableBorrow(this, task)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private val cancelService: CancelService = CancelServiceImpl()
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.CancelService
|
||||
import com.itsaky.androidide.javac.services.NBAttr
|
||||
import com.itsaky.androidide.javac.services.NBClassFinder
|
||||
import com.itsaky.androidide.javac.services.NBClassReader
|
||||
import com.itsaky.androidide.javac.services.NBEnter
|
||||
import com.itsaky.androidide.javac.services.NBJavacTrees
|
||||
import com.itsaky.androidide.javac.services.NBMemberEnter
|
||||
import com.itsaky.androidide.javac.services.NBParserFactory
|
||||
import com.itsaky.androidide.javac.services.NBResolve
|
||||
import com.itsaky.androidide.javac.services.NBTreeMaker
|
||||
import com.itsaky.androidide.javac.services.fs.CacheFSInfoSingleton
|
||||
import com.itsaky.androidide.javac.services.fs.JarPackageProviderImpl
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
import com.itsaky.androidide.zipfs2.JarPackageProvider
|
||||
import jdkx.tools.DiagnosticListener
|
||||
import jdkx.tools.JavaFileManager
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.source.util.JavacTask
|
||||
import openjdk.source.util.TaskEvent
|
||||
import openjdk.source.util.TaskEvent.Kind.ANALYZE
|
||||
import openjdk.source.util.TaskListener
|
||||
import openjdk.tools.javac.api.JavacTrees
|
||||
import openjdk.tools.javac.api.MultiTaskListener
|
||||
import openjdk.tools.javac.code.Types
|
||||
import openjdk.tools.javac.comp.Annotate
|
||||
import openjdk.tools.javac.comp.Check
|
||||
import openjdk.tools.javac.comp.CompileStates
|
||||
import openjdk.tools.javac.comp.Enter
|
||||
import openjdk.tools.javac.comp.Modules
|
||||
import openjdk.tools.javac.file.CacheFSInfo
|
||||
import openjdk.tools.javac.file.FSInfo
|
||||
import openjdk.tools.javac.main.Arguments
|
||||
import openjdk.tools.javac.main.JavaCompiler
|
||||
import openjdk.tools.javac.model.JavacElements
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit
|
||||
import openjdk.tools.javac.util.Context
|
||||
import openjdk.tools.javac.util.DefinedBy
|
||||
import openjdk.tools.javac.util.DefinedBy.Api.COMPILER_TREE
|
||||
import openjdk.tools.javac.util.Log
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Reusable [Context] for [ReusableCompiler].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ReusableContext(cancelService: CancelService) : Context(), TaskListener {
|
||||
|
||||
private val flowCompleted = mutableSetOf<URI>()
|
||||
|
||||
init {
|
||||
put(Log.logKey, ReusableLog.factory)
|
||||
put(FSInfo::class.java, if (VMUtils.isJvm()) CacheFSInfo() else CacheFSInfoSingleton)
|
||||
put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory)
|
||||
put(JavacFlowListener.flowListenerKey, JavacFlowListener { this.hasFlowCompleted(it) })
|
||||
put(JarPackageProvider::class.java, JarPackageProviderImpl)
|
||||
|
||||
NBAttr.preRegister(this)
|
||||
NBParserFactory.preRegister(this)
|
||||
NBTreeMaker.preRegister(this)
|
||||
NBJavacTrees.preRegister(this)
|
||||
NBResolve.preRegister(this)
|
||||
NBEnter.preRegister(this)
|
||||
NBMemberEnter.preRegister(this, false)
|
||||
NBClassFinder.preRegister(this)
|
||||
NBClassReader.preRegister(this)
|
||||
CancelService.preRegister(this, cancelService)
|
||||
}
|
||||
|
||||
@DefinedBy(COMPILER_TREE)
|
||||
override fun started(e: TaskEvent) {
|
||||
// log.debug("Started: $e")
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@DefinedBy(COMPILER_TREE)
|
||||
override fun finished(e: TaskEvent) {
|
||||
if (e.kind == ANALYZE) {
|
||||
val cu = e.compilationUnit as JCCompilationUnit
|
||||
if (cu.sourcefile != null) {
|
||||
flowCompleted.add(cu.sourcefile.toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
drop(Arguments.argsKey)
|
||||
drop(DiagnosticListener::class.java)
|
||||
drop(Log.outKey)
|
||||
drop(Log.errKey)
|
||||
drop(JavaFileManager::class.java)
|
||||
drop(JavacTask::class.java)
|
||||
drop(JavacTrees::class.java)
|
||||
drop(JavacElements::class.java)
|
||||
|
||||
if (ht[Log.logKey] is ReusableLog) {
|
||||
// log already init-ed - not first round
|
||||
(Log.instance(this) as ReusableLog).clear()
|
||||
Enter.instance(this).newRound()
|
||||
(JavaCompiler.instance(this) as ReusableJavaCompiler).clear()
|
||||
Types.instance(this).newRound()
|
||||
Check.instance(this).newRound()
|
||||
Modules.instance(this).newRound()
|
||||
Annotate.instance(this).newRound()
|
||||
CompileStates.instance(this).clear()
|
||||
MultiTaskListener.instance(this).clear()
|
||||
}
|
||||
}
|
||||
|
||||
/** **FOR INTERNAL USE ONLY!** */
|
||||
fun <T> drop(k: Key<T>?) {
|
||||
ht.remove(k)
|
||||
}
|
||||
|
||||
/** **FOR INTERNAL USE ONLY!** */
|
||||
fun <T> drop(c: Class<T>?) {
|
||||
drop(key(c))
|
||||
}
|
||||
|
||||
private fun hasFlowCompleted(fo: JavaFileObject?): Boolean {
|
||||
return if (fo == null) {
|
||||
false
|
||||
} else {
|
||||
try {
|
||||
this.flowCompleted.contains(fo.toUri())
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.NBJavaCompiler
|
||||
import openjdk.tools.javac.main.JavaCompiler
|
||||
import openjdk.tools.javac.util.Context
|
||||
|
||||
/**
|
||||
* Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated
|
||||
* with previous compilations.
|
||||
*/
|
||||
open class ReusableJavaCompiler(context: Context?) : NBJavaCompiler(context) {
|
||||
|
||||
companion object {
|
||||
val factory = Context.Factory<JavaCompiler> { ReusableJavaCompiler(it) }
|
||||
}
|
||||
|
||||
override fun checkReusable() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
newRound()
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.NBLog
|
||||
import jdkx.tools.Diagnostic
|
||||
import jdkx.tools.DiagnosticListener
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.tools.javac.util.Context
|
||||
import openjdk.tools.javac.util.DefinedBy
|
||||
import openjdk.tools.javac.util.DefinedBy.Api.COMPILER
|
||||
import openjdk.tools.javac.util.Log
|
||||
import java.io.PrintWriter
|
||||
|
||||
/**
|
||||
* Reusable Log; exposes a method to clean up the component from leftovers associated with
|
||||
* previous compilations.
|
||||
*/
|
||||
internal class ReusableLog(var context: Context) : NBLog(context, PrintWriter(System.err)) {
|
||||
fun clear() {
|
||||
recorded.clear()
|
||||
sourceMap.clear()
|
||||
nerrors = 0
|
||||
nwarnings = 0
|
||||
|
||||
// Set a fake listener that will lazily lookup the context for the 'real' listener.
|
||||
// Since
|
||||
// this field is never updated when a new task is created, we cannot simply reset
|
||||
// the field
|
||||
// or keep old value. This is a hack to workaround the limitations in the current
|
||||
// infrastructure.
|
||||
diagListener = object : DiagnosticListener<JavaFileObject?> {
|
||||
|
||||
var cachedListener: DiagnosticListener<JavaFileObject>? = null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@DefinedBy(COMPILER)
|
||||
override fun report(diagnostic: Diagnostic<out JavaFileObject>) {
|
||||
if (cachedListener == null) {
|
||||
cachedListener = context.get(DiagnosticListener::class.java) as DiagnosticListener<JavaFileObject>?
|
||||
}
|
||||
cachedListener!!.report(diagnostic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val factory = Context.Factory<Log> { context: Context -> ReusableLog(context) }
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.fs
|
||||
|
||||
import com.itsaky.androidide.zipfs2.AndroidFsProvider
|
||||
import java.nio.file.spi.FileSystemProvider
|
||||
|
||||
/** @author Akash Yadav */
|
||||
object AndroidFsProviderImpl : AndroidFsProvider() {
|
||||
|
||||
init {
|
||||
INSTANCE = this
|
||||
}
|
||||
|
||||
fun init() {
|
||||
// Used to instantiate this class and override the AndroidFsProvider instance
|
||||
}
|
||||
|
||||
override fun zipFsProvider(): FileSystemProvider {
|
||||
return CachingJarFileSystemProvider
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.fs
|
||||
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
import openjdk.tools.javac.file.CacheFSInfo
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Singleton class for [CacheFSInfo] to avoid reading attributes of same file multiple times.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object CacheFSInfoSingleton : CacheFSInfo() {
|
||||
|
||||
const val TEST_PROP_ENABLED_ON_JVM = "ide.testing.javac.fsCache.isEnabledOnJVM"
|
||||
private val log = LoggerFactory.getLogger(CacheFSInfoSingleton::class.java)
|
||||
|
||||
/**
|
||||
* Caches information about the given [Path].
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun cache(file: Path, cacheJarClasspath: Boolean = true) {
|
||||
|
||||
if (System.getProperty(TEST_PROP_ENABLED_ON_JVM, null) != "true") {
|
||||
if (VMUtils.isJvm()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Cache canonical path
|
||||
getCanonicalFile(file)
|
||||
|
||||
// Cache attributes
|
||||
getAttributes(file)
|
||||
|
||||
// Cache jar classpath if requested
|
||||
if (cacheJarClasspath) {
|
||||
getJarClassPath(file)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
log.warn("Failed to cache jar file: {}", file, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.fs
|
||||
|
||||
import com.itsaky.androidide.zipfs2.ZipFileSystem
|
||||
import com.itsaky.androidide.zipfs2.ZipFileSystemProvider
|
||||
import jdkx.lang.model.SourceVersion
|
||||
import openjdk.tools.javac.file.RelativePath.RelativeDirectory
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* A cached file system for JAR files.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CachedJarFileSystem(
|
||||
provider: ZipFileSystemProvider?,
|
||||
zfpath: Path?,
|
||||
env: MutableMap<String, *>?
|
||||
) : ZipFileSystem(provider, zfpath, env) {
|
||||
|
||||
internal val packages = mutableMapOf<RelativeDirectory, Path>()
|
||||
|
||||
override fun close() {
|
||||
// Do nothing
|
||||
// This is called manually by the Java LSP
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun doClose() {
|
||||
super.close()
|
||||
}
|
||||
|
||||
fun storeJARPackageDir(dir: Path?): Boolean {
|
||||
if (isValid(dir?.fileName)) {
|
||||
packages[RelativeDirectory(rootDir.relativize(dir!!).toString())] = dir
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isValid(fileName: Path?): Boolean {
|
||||
return if (fileName == null) {
|
||||
true
|
||||
} else {
|
||||
var name = fileName.toString()
|
||||
if (name.endsWith("/")) {
|
||||
name = name.substring(0, name.length - 1)
|
||||
}
|
||||
SourceVersion.isIdentifier(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.fs
|
||||
|
||||
import com.itsaky.androidide.zipfs2.JarFileSystemProvider
|
||||
import com.itsaky.androidide.zipfs2.ZipFileSystem
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
/**
|
||||
* An implementation of [JarFileSystemProvider] that caches the created [CachedJarFileSystem] so
|
||||
* that it can be (re)used in multiple compilations.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object CachingJarFileSystemProvider : JarFileSystemProvider() {
|
||||
private val cachedFs = ConcurrentHashMap<String, CachedJarFileSystem>()
|
||||
|
||||
private val log = LoggerFactory.getLogger(CachingJarFileSystemProvider::class.java)
|
||||
|
||||
override fun createFs(path: Path, env: MutableMap<String, *>?): ZipFileSystem {
|
||||
val cached = cachedFs[path.normalize().pathString]
|
||||
if (cached != null) {
|
||||
return cached
|
||||
}
|
||||
return createAndCache(path, env)
|
||||
}
|
||||
|
||||
fun newFileSystem(path: Path): FileSystem? {
|
||||
return newFileSystem(path, mutableMapOf<String, Any>())
|
||||
}
|
||||
|
||||
fun clearCache() {
|
||||
cachedFs.values.forEach(this::closeFs)
|
||||
cachedFs.clear()
|
||||
}
|
||||
|
||||
fun clearCaches(predicate: (Path) -> Boolean) {
|
||||
return clearCachesForPaths { predicate(Paths.get(it)) }
|
||||
}
|
||||
|
||||
fun clearCachesForPaths(predicate: (String) -> Boolean) {
|
||||
val toRemove =
|
||||
this.cachedFs.keys.mapNotNull {
|
||||
return@mapNotNull if (predicate(it)) {
|
||||
it
|
||||
} else null
|
||||
}
|
||||
|
||||
if (toRemove.isNotEmpty()) {
|
||||
toRemove.forEach(this::clearCache)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCache(path: Path) {
|
||||
clearCache(path.normalize().pathString)
|
||||
}
|
||||
|
||||
fun clearCache(path: String) {
|
||||
val fs = cachedFs.remove(path)
|
||||
if (fs != null) {
|
||||
log.debug("Clearing cached JAR file system for path: {}", path)
|
||||
closeFs(fs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun closeFs(fs: CachedJarFileSystem) {
|
||||
try {
|
||||
fs.doClose()
|
||||
} catch (err: Throwable) {
|
||||
log.error("Failed to close cached zip file system: {}", fs, err)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAndCache(path: Path, env: MutableMap<String, *>?): CachedJarFileSystem {
|
||||
val fs = CachedJarFileSystem(this, path, env)
|
||||
cachedFs[path.normalize().pathString] = fs
|
||||
return fs
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.fs
|
||||
|
||||
import com.itsaky.androidide.zipfs2.JarPackageProvider
|
||||
import openjdk.tools.javac.file.RelativePath.RelativeDirectory
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Provides cached packages from arhives.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object JarPackageProviderImpl : JarPackageProvider {
|
||||
override fun getPackages(archivePath: Path): MutableMap<RelativeDirectory, Path> {
|
||||
val fs = CachingJarFileSystemProvider.newFileSystem(archivePath) as CachedJarFileSystem
|
||||
return fs.packages
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.partial
|
||||
|
||||
import jdkx.tools.DiagnosticListener
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.source.tree.CompilationUnitTree
|
||||
import openjdk.tools.javac.api.JavacTaskImpl
|
||||
import openjdk.tools.javac.api.JavacTrees
|
||||
|
||||
/**
|
||||
* Information about a compilation.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
data class CompilationInfo(
|
||||
@JvmField val task: JavacTaskImpl,
|
||||
@JvmField val diagnosticListener: DiagnosticListener<JavaFileObject>,
|
||||
@JvmField val cu: CompilationUnitTree,
|
||||
) {
|
||||
val trees: JavacTrees = JavacTrees.instance(task)
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.partial;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.DiagnosticListener;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.tools.javac.api.ClientCodeWrapper;
|
||||
import openjdk.tools.javac.util.JCDiagnostic;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DiagnosticListenerImpl implements DiagnosticListener<JavaFileObject> {
|
||||
|
||||
private final Map<JavaFileObject, Diagnostics> source2Errors;
|
||||
private final JavaFileObject jfo;
|
||||
private volatile List<Diagnostic<? extends JavaFileObject>> partialReparseErrors;
|
||||
/** true if the partialReparseErrors contain some non-warning */
|
||||
private volatile boolean partialReparseRealErrors;
|
||||
|
||||
private volatile List<Diagnostic<? extends JavaFileObject>> affectedErrors;
|
||||
private volatile int currentDelta;
|
||||
|
||||
public DiagnosticListenerImpl(@Nullable final JavaFileObject jfo) {
|
||||
this.jfo = jfo;
|
||||
this.source2Errors = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Diagnostic<? extends JavaFileObject> message) {
|
||||
if (partialReparseErrors != null) {
|
||||
if (this.jfo != null && this.jfo == message.getSource()) {
|
||||
partialReparseErrors.add(message);
|
||||
if (message.getKind() == Diagnostic.Kind.ERROR) {
|
||||
partialReparseRealErrors = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Diagnostics errors = getErrors(message.getSource());
|
||||
errors.add((int) message.getPosition(), message);
|
||||
}
|
||||
}
|
||||
|
||||
private Diagnostics getErrors(JavaFileObject file) {
|
||||
Diagnostics errors;
|
||||
if (isIncompleteClassPath()) {
|
||||
// if (root != null && JavaIndex.hasSourceCache(root.toURL(), false)) {
|
||||
// errors = source2Errors.get(file);
|
||||
// if (errors == null) {
|
||||
// source2Errors.put(file, errors = new Diagnostics());
|
||||
// if (this.jfo != null && this.jfo == file) {
|
||||
// errors.add(0, new IncompleteClassPath(this.jfo));
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
errors = new Diagnostics();
|
||||
if (this.jfo != null && this.jfo == file) {
|
||||
errors.add(0, new IncompleteClassPath(this.jfo));
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
errors = source2Errors.get(file);
|
||||
if (errors == null) {
|
||||
source2Errors.put(file, errors = new Diagnostics());
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private boolean isIncompleteClassPath() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public final boolean hasPartialReparseErrors() {
|
||||
return this.partialReparseErrors != null && partialReparseRealErrors;
|
||||
}
|
||||
|
||||
public final void startPartialReparse(int from, int to) {
|
||||
if (partialReparseErrors == null) {
|
||||
partialReparseErrors = new ArrayList<>();
|
||||
Diagnostics errors = getErrors(jfo);
|
||||
SortedMap<Integer, Collection<DiagNode>> subMap = errors.subMap(from, to);
|
||||
subMap.values().forEach(value -> value.forEach(errors::unlink));
|
||||
subMap.clear(); // Remove errors in changed method durring the partial reparse
|
||||
Map<Integer, Collection<DiagNode>> tail = errors.tailMap(to);
|
||||
this.affectedErrors = new ArrayList<>(tail.size());
|
||||
HashSet<DiagNode> tailNodes = new HashSet<>();
|
||||
for (Iterator<Map.Entry<Integer, Collection<DiagNode>>> it = tail.entrySet().iterator();
|
||||
it.hasNext(); ) {
|
||||
tailNodes.addAll(it.next().getValue());
|
||||
it.remove();
|
||||
}
|
||||
DiagNode node = errors.first;
|
||||
while (node != null) {
|
||||
if (tailNodes.contains(node)) {
|
||||
errors.unlink(node);
|
||||
final JCDiagnostic diagnostic;
|
||||
if (node.diag instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) {
|
||||
diagnostic = ((ClientCodeWrapper.DiagnosticSourceUnwrapper) node.diag).d;
|
||||
} else {
|
||||
diagnostic = (JCDiagnostic) node.diag;
|
||||
}
|
||||
if (diagnostic == null) {
|
||||
throw new IllegalStateException(
|
||||
"diagnostic == null " + mapArraysToLists(Thread.getAllStackTraces())); // NOI18N
|
||||
}
|
||||
this.affectedErrors.add(new D(diagnostic));
|
||||
}
|
||||
node = node.next;
|
||||
}
|
||||
} else {
|
||||
this.partialReparseErrors.clear();
|
||||
}
|
||||
partialReparseRealErrors = false;
|
||||
}
|
||||
|
||||
private static <A, B> Map<A, List<B>> mapArraysToLists(final Map<? extends A, B[]> map) {
|
||||
final Map<A, List<B>> result = new HashMap<>();
|
||||
for (Map.Entry<? extends A, B[]> entry : map.entrySet()) {
|
||||
result.put(entry.getKey(), Arrays.asList(entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public final void endPartialReparse(final int delta) {
|
||||
this.currentDelta += delta;
|
||||
}
|
||||
|
||||
private static final class D implements Diagnostic {
|
||||
|
||||
private final JCDiagnostic delegate;
|
||||
|
||||
public D(final JCDiagnostic delegate) {
|
||||
assert delegate != null;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return this.delegate.getKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return this.delegate.getSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return this.delegate.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartPosition() {
|
||||
return this.delegate.getStartPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndPosition() {
|
||||
return this.delegate.getEndPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLineNumber() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getColumnNumber() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return this.delegate.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(Locale locale) {
|
||||
return this.delegate.getMessage(locale);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IncompleteClassPath implements Diagnostic<JavaFileObject> {
|
||||
|
||||
private final JavaFileObject file;
|
||||
|
||||
IncompleteClassPath(final JavaFileObject file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.WARNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getSource() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartPosition() {
|
||||
return getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndPosition() {
|
||||
return getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLineNumber() {
|
||||
return getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getColumnNumber() {
|
||||
return getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "nb.classpath.incomplete"; // NOI18N
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(Locale locale) {
|
||||
return "Incomplete classpath";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Diagnostics extends TreeMap<Integer, Collection<DiagNode>> {
|
||||
private DiagNode first;
|
||||
private DiagNode last;
|
||||
|
||||
public void add(int pos, Diagnostic<? extends JavaFileObject> diag) {
|
||||
Collection<DiagNode> nodes = get((int) diag.getPosition());
|
||||
if (nodes == null) {
|
||||
put((int) diag.getPosition(), nodes = new ArrayList<>());
|
||||
}
|
||||
DiagNode node = new DiagNode(last, diag, null);
|
||||
nodes.add(node);
|
||||
if (last != null) {
|
||||
last.next = node;
|
||||
}
|
||||
last = node;
|
||||
if (first == null) {
|
||||
first = node;
|
||||
}
|
||||
}
|
||||
|
||||
private void unlink(DiagNode node) {
|
||||
if (node.next == null) {
|
||||
last = node.prev;
|
||||
} else {
|
||||
node.next.prev = node.prev;
|
||||
}
|
||||
if (node.prev == null) {
|
||||
first = node.next;
|
||||
} else {
|
||||
node.prev.next = node.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DiagNode {
|
||||
private final Diagnostic<? extends JavaFileObject> diag;
|
||||
private DiagNode next;
|
||||
private DiagNode prev;
|
||||
|
||||
private DiagNode(DiagNode prev, Diagnostic<? extends JavaFileObject> diag, DiagNode next) {
|
||||
this.diag = diag;
|
||||
this.next = next;
|
||||
this.prev = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.partial
|
||||
|
||||
import openjdk.source.util.TreePath
|
||||
|
||||
/**
|
||||
* A parser reparser parses part of code.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface PartialReparser {
|
||||
fun reparseMethod(ci: CompilationInfo, methodPath: TreePath, newBody: String, fileContents: CharSequence): Boolean
|
||||
}
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.partial
|
||||
|
||||
import com.itsaky.androidide.javac.services.compiler.JavacFlowListener
|
||||
import com.itsaky.androidide.javac.services.util.ReparserUtils
|
||||
import com.itsaky.androidide.javac.services.visitors.FindAnonymousVisitor
|
||||
import com.itsaky.androidide.javac.services.visitors.TranslateMethodPositionsVisitor
|
||||
import openjdk.source.tree.BlockTree
|
||||
import openjdk.source.tree.ClassTree
|
||||
import openjdk.source.tree.CompilationUnitTree
|
||||
import openjdk.source.tree.Tree.Kind.BLOCK
|
||||
import openjdk.source.tree.Tree.Kind.METHOD
|
||||
import openjdk.source.util.TreePath
|
||||
import openjdk.tools.javac.api.JavacScope
|
||||
import openjdk.tools.javac.api.JavacTrees
|
||||
import openjdk.tools.javac.code.Flags
|
||||
import openjdk.tools.javac.code.Symbol
|
||||
import openjdk.tools.javac.code.Symtab
|
||||
import openjdk.tools.javac.comp.Attr
|
||||
import openjdk.tools.javac.comp.AttrContext
|
||||
import openjdk.tools.javac.comp.Enter
|
||||
import openjdk.tools.javac.comp.Env
|
||||
import openjdk.tools.javac.comp.Flow
|
||||
import openjdk.tools.javac.parser.JavacParser
|
||||
import openjdk.tools.javac.parser.LazyDocCommentTable.Entry
|
||||
import openjdk.tools.javac.parser.ScannerFactory
|
||||
import openjdk.tools.javac.tree.DocCommentTable
|
||||
import openjdk.tools.javac.tree.EndPosTable
|
||||
import openjdk.tools.javac.tree.JCTree
|
||||
import openjdk.tools.javac.tree.JCTree.JCBlock
|
||||
import openjdk.tools.javac.tree.JCTree.JCClassDecl
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit
|
||||
import openjdk.tools.javac.tree.JCTree.JCMethodDecl
|
||||
import openjdk.tools.javac.tree.TreeInfo
|
||||
import openjdk.tools.javac.tree.TreeMaker
|
||||
import openjdk.tools.javac.util.Context
|
||||
import openjdk.tools.javac.util.List
|
||||
import openjdk.tools.javac.util.Log
|
||||
import openjdk.tools.javac.util.Names
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.CharBuffer
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* Partial reparser implementation.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class PartialReparserImpl : PartialReparser {
|
||||
|
||||
private var allowPartialReparse: Boolean = ReparserUtils.canReparse()
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(PartialReparserImpl::class.java)
|
||||
}
|
||||
|
||||
override fun reparseMethod(
|
||||
ci: CompilationInfo,
|
||||
methodPath: TreePath,
|
||||
newBody: String,
|
||||
fileContents: CharSequence
|
||||
): Boolean {
|
||||
|
||||
if (!allowPartialReparse) {
|
||||
log.debug("Partial reparse is disabled")
|
||||
return false
|
||||
}
|
||||
|
||||
val cu = ci.cu
|
||||
val fo = cu.sourceFile
|
||||
val task = ci.task
|
||||
val method = methodPath.leaf as JCMethodDecl
|
||||
if (methodPath.leaf.kind != METHOD) {
|
||||
log.warn("Provided TreePath does not correspond to a MethodTree")
|
||||
return false
|
||||
}
|
||||
|
||||
val methodScope = ci.trees.getScope(TreePath(methodPath, method.body))
|
||||
val jt = JavacTrees.instance(task)
|
||||
val origStartPos = jt.sourcePositions.getStartPosition(cu, method.body).toInt()
|
||||
val origEndPos = jt.sourcePositions.getEndPosition(cu, method.body).toInt()
|
||||
if (origStartPos < 0) {
|
||||
log.warn("Javac returned start position {} < 0", origStartPos)
|
||||
return false
|
||||
}
|
||||
|
||||
if (origStartPos > origEndPos) {
|
||||
log.warn("Java returned start position: {} > end position: {}", origStartPos, origEndPos)
|
||||
return false
|
||||
}
|
||||
|
||||
val fav = FindAnonymousVisitor()
|
||||
fav.scan(method.body, null)
|
||||
if (fav.hasLocalClass) {
|
||||
log.debug("Skipped reparse method. Old local classes: {}", fo)
|
||||
return false
|
||||
}
|
||||
|
||||
val noInner = fav.noInner
|
||||
val context = task.context
|
||||
|
||||
try {
|
||||
val l = com.itsaky.androidide.javac.services.NBLog.instance(context)
|
||||
l.startPartialReparse(fo)
|
||||
val prevLogged = l.useSource(fo)
|
||||
val block: JCBlock?
|
||||
|
||||
try {
|
||||
val dl = ci.diagnosticListener as DiagnosticListenerImpl
|
||||
dl.startPartialReparse(origStartPos, origEndPos)
|
||||
val docComments = HashMap<JCTree, Entry>()
|
||||
log.debug("Reparse method...")
|
||||
block = reparseMethodBody(context, cu, method, newBody, docComments)
|
||||
val endPosTable = (cu as JCCompilationUnit).endPositions
|
||||
if (block == null) {
|
||||
log.debug("Skipped reparse method. Invalid position, newBody: {}", newBody)
|
||||
return false
|
||||
}
|
||||
|
||||
val sourcePositions = jt.sourcePositions
|
||||
val newEndPos = sourcePositions.getEndPosition(cu, block).toInt()
|
||||
if (newEndPos != origStartPos + newBody.length) {
|
||||
log.warn("Invalid position in reparsed method")
|
||||
return false
|
||||
}
|
||||
|
||||
fav.reset()
|
||||
fav.scan(block, null)
|
||||
|
||||
val newNoInner = fav.noInner
|
||||
if (fav.hasLocalClass || noInner != newNoInner) {
|
||||
log.debug("Skipped method reparse (new local class): {}", fo)
|
||||
return false
|
||||
}
|
||||
|
||||
val docCommentsTable = getLazyDocCommentsTable(cu)
|
||||
docCommentsTable.keys.removeAll(fav.docOwners)
|
||||
docCommentsTable.putAll(docComments)
|
||||
val delta = newEndPos - origEndPos
|
||||
val tpv = TranslateMethodPositionsVisitor(method, endPosTable, delta)
|
||||
tpv.scan(cu, null)
|
||||
doUnenter(context, cu, method)
|
||||
method.body = block
|
||||
|
||||
log.debug("ReAttr method...")
|
||||
reAttrMethodBody(context, methodScope, method, block)
|
||||
|
||||
if (!dl.hasPartialReparseErrors()) {
|
||||
val fl = JavacFlowListener.instance(context)
|
||||
if (fl != null && fl.hasFlowCompleted(cu.sourceFile)) {
|
||||
log.debug("Reflow method...")
|
||||
val tp = TreePath.getPath(cu, method)
|
||||
val t = tp.parentPath.leaf as ClassTree
|
||||
reflowMethodBody(context, t, method)
|
||||
}
|
||||
}
|
||||
|
||||
val arr = CharArray(fileContents.length)
|
||||
for (index in fileContents.indices) {
|
||||
arr[index] = fileContents[index]
|
||||
}
|
||||
|
||||
buildLineMap(cu, arr)
|
||||
dl.endPartialReparse(delta)
|
||||
} finally {
|
||||
l.endPartialReparse(cu.sourceFile)
|
||||
l.useSource(prevLogged)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
log.error("An error occurred while reparsing method", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun buildLineMap(cu: JCCompilationUnit, arr: CharArray) {
|
||||
ReparserUtils.buildLineMap(cu.getLineMap(), arr, arr.size)
|
||||
}
|
||||
|
||||
private fun getLazyDocCommentsTable(cu: JCCompilationUnit): MutableMap<JCTree, Entry> {
|
||||
return getLazyDocCommentsTable(cu.docComments)
|
||||
}
|
||||
|
||||
private fun getLazyDocCommentsTable(docs: DocCommentTable): MutableMap<JCTree, Entry> {
|
||||
return ReparserUtils.getLazyDocCommentsTable(docs)!!
|
||||
}
|
||||
|
||||
private fun doUnenter(context: Context?, cu: CompilationUnitTree, method: JCMethodDecl) {
|
||||
if (cu !is JCCompilationUnit) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
|
||||
ReparserUtils.unenter(context, cu, method.body)
|
||||
}
|
||||
|
||||
private fun reparseMethodBody(
|
||||
context: Context,
|
||||
cu: CompilationUnitTree,
|
||||
method: JCMethodDecl,
|
||||
newBody: String,
|
||||
docComments: MutableMap<JCTree, Entry>,
|
||||
): JCBlock? {
|
||||
val startPos = method.body.pos
|
||||
val body = CharArray(startPos + newBody.length + 1)
|
||||
Arrays.fill(body, ' ')
|
||||
for (index in newBody.indices) {
|
||||
body[startPos + index] = newBody[index]
|
||||
}
|
||||
body[startPos + newBody.length] = '\u0000'
|
||||
val buf = CharBuffer.wrap(body, 0, body.size - 1)
|
||||
val parser = newParser(context, buf, (cu as JCCompilationUnit).endPositions)
|
||||
val statement = parser.parseStatement()
|
||||
if (statement.kind == BLOCK) {
|
||||
docComments.putAll(getLazyDocCommentsTable(getParserDocComments(parser)))
|
||||
return statement as JCBlock
|
||||
}
|
||||
|
||||
log.warn("JavacParser parsed invalid statment. Block statement was expected...")
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getParserDocComments(parser: JavacParser): DocCommentTable {
|
||||
return ReparserUtils.getDocComments(parser)!!
|
||||
}
|
||||
|
||||
private fun newParser(
|
||||
context: Context,
|
||||
buf: CharBuffer?,
|
||||
endPositions: EndPosTable?,
|
||||
): JavacParser {
|
||||
val factory =
|
||||
com.itsaky.androidide.javac.services.NBParserFactory.instance(context)
|
||||
as com.itsaky.androidide.javac.services.NBParserFactory
|
||||
val scannerFactory = ScannerFactory.instance(context)
|
||||
val cancelService = com.itsaky.androidide.javac.services.CancelService.instance(context)
|
||||
val lexer = scannerFactory.newScanner(buf, true)
|
||||
if (
|
||||
endPositions
|
||||
is com.itsaky.androidide.javac.services.NBParserFactory.NBJavacParser.EndPosTableImpl
|
||||
) {
|
||||
endPositions.resetErrorEndPos()
|
||||
}
|
||||
|
||||
return object :
|
||||
com.itsaky.androidide.javac.services.NBParserFactory.NBJavacParser(
|
||||
factory,
|
||||
lexer,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
cancelService
|
||||
) {
|
||||
override fun newEndPosTable(keepEndPositions: Boolean): AbstractEndPosTable {
|
||||
return object : AbstractEndPosTable(this) {
|
||||
override fun storeEnd(tree: JCTree?, endpos: Int) {
|
||||
(endPositions as EndPosTableImpl).storeEnd(tree, endpos)
|
||||
}
|
||||
|
||||
override fun <T : JCTree?> to(t: T): T {
|
||||
storeEnd(t, token.endPos)
|
||||
return t
|
||||
}
|
||||
|
||||
override fun <T : JCTree?> toP(t: T): T {
|
||||
storeEnd(t, S.prevToken().endPos)
|
||||
return t
|
||||
}
|
||||
|
||||
override fun getEndPos(tree: JCTree?): Int {
|
||||
return endPositions!!.getEndPos(tree)
|
||||
}
|
||||
|
||||
override fun replaceTree(oldtree: JCTree?, newtree: JCTree?): Int {
|
||||
return endPositions!!.replaceTree(oldtree, newtree)
|
||||
}
|
||||
|
||||
override fun setErrorEndPos(errPos: Int) {
|
||||
super.setErrorEndPos(errPos)
|
||||
(endPositions as EndPosTableImpl).setErrorEndPos(errPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reAttrMethodBody(
|
||||
context: Context,
|
||||
scope: JavacScope,
|
||||
tree: JCMethodDecl,
|
||||
block: JCBlock,
|
||||
): JCBlock {
|
||||
val attr = Attr.instance(context)
|
||||
val names: Names = Names.instance(context)
|
||||
val syms: Symtab = Symtab.instance(context)
|
||||
val log: Log = Log.instance(context)
|
||||
val make: TreeMaker = TreeMaker.instance(context)
|
||||
val env: Env<AttrContext> = scope.env // this is a copy anyway...
|
||||
val owner: Symbol.ClassSymbol = env.enclClass.sym
|
||||
|
||||
if (tree.name === names.init && !owner.type.isErroneous && owner.type !== syms.objectType) {
|
||||
val body = tree.body
|
||||
if (body.stats.isEmpty() || !TreeInfo.isSelfCall(body.stats.head)) {
|
||||
body.stats =
|
||||
body.stats.prepend(
|
||||
make.at(body.pos).Exec(make.Apply(List.nil(), make.Ident(names._super), List.nil()))
|
||||
)
|
||||
} else if (
|
||||
(env.enclClass.sym.flags() and Flags.ENUM.toLong()) != 0L &&
|
||||
(tree.mods.flags and Flags.GENERATEDCONSTR) == 0L &&
|
||||
TreeInfo.isSuperCall(body.stats.head)
|
||||
) {
|
||||
// enum constructors are not allowed to call super
|
||||
// directly, so make sure there aren't any super calls
|
||||
// in enum constructorObjectCheckers, except in the compiler
|
||||
// generated one.
|
||||
log.error(
|
||||
tree.body.stats.head.pos(),
|
||||
openjdk.tools.javac.util.JCDiagnostic.Error(
|
||||
"compiler",
|
||||
"call.to.super.not.allowed.in.enum.ctor",
|
||||
env.enclClass.sym
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
attr.attribStat(block, env)
|
||||
return block
|
||||
}
|
||||
|
||||
private fun reflowMethodBody(
|
||||
context: Context,
|
||||
ownerClass: ClassTree,
|
||||
decl: JCMethodDecl
|
||||
): BlockTree {
|
||||
val flow = Flow.instance(context)
|
||||
val maker = TreeMaker.instance(context)
|
||||
val enter = Enter.instance(context)
|
||||
flow.analyzeTree(enter.getEnv((ownerClass as JCClassDecl).sym), maker)
|
||||
return decl.body
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.partial;
|
||||
|
||||
import java.util.Locale;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import openjdk.tools.javac.api.DiagnosticFormatter;
|
||||
import openjdk.tools.javac.util.JCDiagnostic;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class RichDiagnostic implements Diagnostic {
|
||||
|
||||
private final JCDiagnostic delegate;
|
||||
private final DiagnosticFormatter<JCDiagnostic> formatter;
|
||||
|
||||
public RichDiagnostic(JCDiagnostic delegate, DiagnosticFormatter<JCDiagnostic> formatter) {
|
||||
this.delegate = delegate;
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Diagnostic.Kind getKind() {
|
||||
return delegate.getKind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return delegate.getSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return delegate.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartPosition() {
|
||||
return delegate.getStartPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndPosition() {
|
||||
return delegate.getEndPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLineNumber() {
|
||||
return delegate.getLineNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getColumnNumber() {
|
||||
return delegate.getColumnNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return delegate.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(Locale locale) {
|
||||
return formatter.format(delegate, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
|
||||
JCDiagnostic getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public static Diagnostic wrap(Diagnostic d, DiagnosticFormatter<JCDiagnostic> df) {
|
||||
if (d instanceof JCDiagnostic) {
|
||||
return new RichDiagnostic((JCDiagnostic) d, df);
|
||||
} else {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.util
|
||||
|
||||
import jdkx.tools.Diagnostic
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.tools.javac.api.ClientCodeWrapper
|
||||
import openjdk.tools.javac.util.JCDiagnostic
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class JavaDiagnosticUtils {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun asJCDiagnostic(diagnostic: Diagnostic<out JavaFileObject>): JCDiagnostic? {
|
||||
if (diagnostic is JCDiagnostic) {
|
||||
return diagnostic
|
||||
} else if (diagnostic is ClientCodeWrapper.DiagnosticSourceUnwrapper) {
|
||||
return diagnostic.d
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun asUnwrapper(
|
||||
diagnostic: Diagnostic<out JavaFileObject>
|
||||
): ClientCodeWrapper.DiagnosticSourceUnwrapper? {
|
||||
if (diagnostic is ClientCodeWrapper.DiagnosticSourceUnwrapper) {
|
||||
return diagnostic
|
||||
} else if (diagnostic is JCDiagnostic) {
|
||||
return wrap(diagnostic)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun wrap(diagnostic: JCDiagnostic): ClientCodeWrapper.DiagnosticSourceUnwrapper {
|
||||
val klass = ClientCodeWrapper.DiagnosticSourceUnwrapper::class.java
|
||||
val construct = klass.getDeclaredConstructor(JCDiagnostic::class.java)
|
||||
construct.isAccessible = true
|
||||
return construct.newInstance(diagnostic)
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.itsaky.androidide.javac.services.visitors.UnEnter;
|
||||
import com.itsaky.androidide.utils.VMUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import openjdk.tools.javac.comp.Enter;
|
||||
import openjdk.tools.javac.parser.JavacParser;
|
||||
import openjdk.tools.javac.parser.LazyDocCommentTable;
|
||||
import openjdk.tools.javac.tree.DocCommentTable;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Position;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ReparserUtils {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReparserUtils.class);
|
||||
|
||||
private static final boolean isAndroid = !VMUtils.isJvm();
|
||||
private static boolean needUnenterScanner = true;
|
||||
private static Method unenter = null;
|
||||
private static Method lineMapBuild = null;
|
||||
private static Field lazyDocCommentsTable = null;
|
||||
private static Field parserDocComments = null;
|
||||
|
||||
public static boolean canReparse() {
|
||||
if (isAndroid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
initIfNecessary();
|
||||
return unenter != null
|
||||
&& lineMapBuild != null
|
||||
&& lazyDocCommentsTable != null
|
||||
&& parserDocComments != null;
|
||||
}
|
||||
|
||||
public static void buildLineMap(Position.LineMap lines, char[] arr, int size) {
|
||||
if (!canReparse()) {
|
||||
LOG.warn("Cannot perform reparse");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAndroid) {
|
||||
((Position.LineMapImpl) lines).build(arr, size);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
lineMapBuild.invoke(lines, arr, size);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to build line map", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<JCTree, LazyDocCommentTable.Entry> getLazyDocCommentsTable(
|
||||
DocCommentTable docs) {
|
||||
if (!canReparse()) {
|
||||
LOG.warn("Cannot perform reparse");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAndroid) {
|
||||
return ((LazyDocCommentTable) docs).table;
|
||||
}
|
||||
|
||||
try {
|
||||
return (Map<JCTree, LazyDocCommentTable.Entry>) lazyDocCommentsTable.get(docs);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Cannot get doc comment table", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unenter(Context context, JCTree.JCCompilationUnit cu, JCTree tree) {
|
||||
if (!canReparse()) {
|
||||
LOG.warn("Cannot perform reparse");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAndroid) {
|
||||
Enter.instance(context).unenter(cu, tree);
|
||||
return;
|
||||
}
|
||||
|
||||
if (needUnenterScanner) {
|
||||
new UnEnter(Enter.instance(context), cu.modle).scan(tree);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
unenter.invoke(Enter.instance(context), cu, tree);
|
||||
} catch (Throwable err) {
|
||||
LOG.error("Cannot perform unenter", err);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static DocCommentTable getDocComments(JavacParser parser) {
|
||||
if (!canReparse()) {
|
||||
LOG.warn("Cannot perform reparse");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAndroid) {
|
||||
return parser.getDocComments();
|
||||
}
|
||||
|
||||
try {
|
||||
return (DocCommentTable) parserDocComments.get(parser);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unablet to get DocCommentTable", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initIfNecessary() {
|
||||
if (isAndroid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (unenter == null) {
|
||||
unenter =
|
||||
tryReflectMethod(Enter.class, "unenter", JCTree.JCCompilationUnit.class, JCTree.class);
|
||||
needUnenterScanner = unenter == null;
|
||||
}
|
||||
|
||||
if (lineMapBuild == null) {
|
||||
lineMapBuild =
|
||||
tryReflectMethod(
|
||||
"openjdk.tools.javac.util.Position$LineMapImpl", "build", char[].class, int.class);
|
||||
}
|
||||
|
||||
if (lazyDocCommentsTable == null) {
|
||||
lazyDocCommentsTable = tryReflectField(LazyDocCommentTable.class, "table");
|
||||
}
|
||||
|
||||
if (parserDocComments == null) {
|
||||
parserDocComments = tryReflectField(JavacParser.class, "docComments");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
@Nullable
|
||||
private static Method tryReflectMethod(String klass, String methodName, Class<?>... argTypes) {
|
||||
try {
|
||||
return tryReflectMethod(Class.forName(klass), methodName, argTypes);
|
||||
} catch (ClassNotFoundException noMethod) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Method tryReflectMethod(Class<?> klass, String methodName, Class<?>... argTypes) {
|
||||
try {
|
||||
final Method method = klass.getDeclaredMethod(methodName, argTypes);
|
||||
if (!method.isAccessible()) {
|
||||
method.setAccessible(true);
|
||||
}
|
||||
return method;
|
||||
} catch (NoSuchMethodException noMethod) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Field tryReflectField(Class<?> klass, String fieldName) {
|
||||
try {
|
||||
final Field field = klass.getDeclaredField(fieldName);
|
||||
if (!field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
return field;
|
||||
} catch (NoSuchFieldException noMethod) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.visitors
|
||||
|
||||
import openjdk.source.tree.ErroneousTree
|
||||
import openjdk.source.util.TreeScanner
|
||||
|
||||
/** @author Akash Yadav */
|
||||
open class ErrorAwareTreeScanner<R, P> : TreeScanner<R, P>() {
|
||||
override fun visitErroneous(node: ErroneousTree?, p: P): R {
|
||||
if (node == null) {
|
||||
return super.visitErroneous(node, p)
|
||||
}
|
||||
return scan(node.errorTrees, p)
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.visitors
|
||||
|
||||
import com.itsaky.androidide.javac.services.visitors.FindAnonymousVisitor.Mode.CHECK
|
||||
import com.itsaky.androidide.javac.services.visitors.FindAnonymousVisitor.Mode.COLLECT
|
||||
import openjdk.source.tree.ClassTree
|
||||
import openjdk.source.tree.MethodTree
|
||||
import openjdk.source.tree.Tree
|
||||
import openjdk.source.tree.VariableTree
|
||||
|
||||
/**
|
||||
* Partial reparse helper visitor. Finds anonymous and local classes in given method tree.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class FindAnonymousVisitor : ErrorAwareTreeScanner<Unit, Unit>() {
|
||||
|
||||
private enum class Mode {
|
||||
COLLECT,
|
||||
CHECK
|
||||
}
|
||||
|
||||
var noInner = 0
|
||||
var hasLocalClass = false
|
||||
val docOwners: MutableSet<Tree> = HashSet<Tree>()
|
||||
private var mode = COLLECT
|
||||
|
||||
fun reset() {
|
||||
noInner = 0
|
||||
hasLocalClass = false
|
||||
mode = CHECK
|
||||
}
|
||||
|
||||
override fun visitClass(node: ClassTree, p: Unit?): Unit? {
|
||||
if (node.simpleName.isNotEmpty()) {
|
||||
hasLocalClass = true
|
||||
}
|
||||
|
||||
noInner++
|
||||
handleDoc(node)
|
||||
return super.visitClass(node, p)
|
||||
}
|
||||
|
||||
override fun visitMethod(node: MethodTree, p: Unit?): Unit? {
|
||||
handleDoc(node)
|
||||
return super.visitMethod(node, p)
|
||||
}
|
||||
|
||||
override fun visitVariable(node: VariableTree, p: Unit?): Unit? {
|
||||
handleDoc(node)
|
||||
return super.visitVariable(node, p)
|
||||
}
|
||||
|
||||
private fun handleDoc(tree: Tree) {
|
||||
if (mode == COLLECT) {
|
||||
docOwners.add(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.javac.services.visitors;
|
||||
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.source.tree.MethodTree;
|
||||
import openjdk.source.tree.Tree;
|
||||
import openjdk.source.tree.VariableTree;
|
||||
import openjdk.tools.javac.tree.EndPosTable;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
|
||||
/** Helper visitor for partial reparse. Updates tree positions by the given delta. */
|
||||
public class TranslateMethodPositionsVisitor extends ErrorAwareTreeScanner<Void, Void> {
|
||||
|
||||
private final MethodTree changedMethod;
|
||||
private final EndPosTable endPos;
|
||||
private final int delta;
|
||||
boolean active;
|
||||
boolean inMethod;
|
||||
|
||||
public TranslateMethodPositionsVisitor(
|
||||
final MethodTree changedMethod, final EndPosTable endPos, final int delta) {
|
||||
assert endPos != null;
|
||||
this.changedMethod = changedMethod;
|
||||
this.endPos = endPos;
|
||||
this.delta = delta;
|
||||
|
||||
active = changedMethod == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void scan(Tree node, Void p) {
|
||||
if (active && node != null) {
|
||||
if (((JCTree) node).pos >= 0) {
|
||||
((JCTree) node).pos += delta;
|
||||
}
|
||||
}
|
||||
Void result = super.scan(node, p);
|
||||
if (inMethod && node != null) {
|
||||
endPos.replaceTree((JCTree) node, null);
|
||||
}
|
||||
if (active && node != null) {
|
||||
int pos = endPos.replaceTree((JCTree) node, null);
|
||||
int newPos;
|
||||
if (pos < 0) {
|
||||
newPos = pos;
|
||||
} else {
|
||||
newPos = pos + delta;
|
||||
}
|
||||
endPos.storeEnd((JCTree) node, newPos);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
|
||||
return scan(node.getTypeDecls(), p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMethod(MethodTree node, Void p) {
|
||||
if (active || inMethod) {
|
||||
scan(node.getModifiers(), p);
|
||||
scan(node.getReturnType(), p);
|
||||
scan(node.getTypeParameters(), p);
|
||||
scan(node.getParameters(), p);
|
||||
scan(node.getThrows(), p);
|
||||
}
|
||||
if (node == changedMethod) {
|
||||
inMethod = true;
|
||||
}
|
||||
if (active || inMethod) {
|
||||
scan(node.getBody(), p);
|
||||
}
|
||||
if (inMethod) {
|
||||
active = true;
|
||||
inMethod = false;
|
||||
}
|
||||
if (active) {
|
||||
scan(node.getDefaultValue(), p);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariable(VariableTree node, Void p) {
|
||||
JCVariableDecl varDecl = (JCVariableDecl) node;
|
||||
if (varDecl.sym != null && active && varDecl.sym.pos >= 0) {
|
||||
varDecl.sym.pos += delta;
|
||||
}
|
||||
return super.visitVariable(node, p);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.javac.services.visitors
|
||||
|
||||
import com.blankj.utilcode.util.ReflectUtils
|
||||
import openjdk.tools.javac.code.Symbol.ModuleSymbol
|
||||
import openjdk.tools.javac.comp.Enter
|
||||
import openjdk.tools.javac.tree.JCTree.JCClassDecl
|
||||
import openjdk.tools.javac.tree.TreeScanner
|
||||
|
||||
/**
|
||||
* `Enter.unenter` method is not available in JDK 11. This visitor does the same thing as calling
|
||||
* `Enter.unenter` in JDK 17.
|
||||
*
|
||||
* This is never used in the Android Runtime.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class UnEnter(private val enter: Enter, private val msym: ModuleSymbol) : TreeScanner() {
|
||||
override fun visitClassDef(tree: JCClassDecl) {
|
||||
val csym = tree.sym ?: return
|
||||
|
||||
val etr = ReflectUtils.reflect(enter)
|
||||
ReflectUtils.reflect(etr.field("typeEnvs")).method("remove", csym)
|
||||
|
||||
val chk = etr.field("chk")
|
||||
ReflectUtils.reflect(chk).method("removeCompiled", csym)
|
||||
ReflectUtils.reflect(chk).method("clearLocalClassNameIndexes", csym)
|
||||
|
||||
ReflectUtils.reflect(etr.field("syms")).method("removeClass", msym, csym.flatname)
|
||||
super.visitClassDef(tree)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import com.itsaky.androidide.build.config.BuildConfig
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("realm-android")
|
||||
}
|
||||
|
||||
groupConfig {
|
||||
groupIdSuffix.set("event")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "${BuildConfig.packageName}.lsp.java"
|
||||
|
||||
sourceSets {
|
||||
getByName("androidTest") {
|
||||
assets.srcDirs(rootProject.file("subprojects/framework-stubs/libs"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("eventBusIndex", "${BuildConfig.packageName}.events.LspJavaEventsIndex")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt(projects.annotation.processors)
|
||||
|
||||
implementation(libs.androidide.ts)
|
||||
implementation(libs.androidide.ts.java)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.common.editor)
|
||||
implementation(libs.common.javaparser)
|
||||
implementation(libs.common.utilcode)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.google.guava)
|
||||
implementation(libs.google.gson)
|
||||
implementation(libs.google.material)
|
||||
|
||||
implementation(projects.core.actions)
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.indexing)
|
||||
implementation(projects.core.lspApi)
|
||||
implementation(projects.core.resources)
|
||||
implementation(projects.editor.api)
|
||||
implementation(projects.java.javacServices)
|
||||
|
||||
implementation(libs.composite.javac)
|
||||
implementation(libs.composite.javapoet)
|
||||
implementation(libs.composite.jaxp)
|
||||
implementation(libs.composite.jdkJdeps)
|
||||
implementation(libs.composite.googleJavaFormat)
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.common.kotlin)
|
||||
|
||||
testImplementation(projects.testing.commonTest)
|
||||
testImplementation(projects.testing.lspTest)
|
||||
|
||||
androidTestImplementation(projects.testing.androidTest)
|
||||
androidTestImplementation(projects.utilities.shared)
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaIndexingRealmModule
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
|
||||
import com.itsaky.androidide.testing.android.rules.RealmDBTestRule
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmObject
|
||||
import openjdk.tools.classfile.AccessFlags
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class JavaClassCRUDTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val dbTestRule = RealmDBTestRule(JavaIndexingRealmModule())
|
||||
|
||||
@Test
|
||||
fun testSimpleJavaClassCRUD() {
|
||||
dbTestRule.withDb("java-class-CRUD") {
|
||||
val klass = ModelBuilder.createJavaClass()
|
||||
|
||||
// CREATE
|
||||
executeTransaction {
|
||||
it.insert(klass)
|
||||
}
|
||||
|
||||
var dbKlass: JavaClass? = null
|
||||
executeTransaction {
|
||||
// READ
|
||||
dbKlass = where(JavaClass::class.java)
|
||||
.equalTo("fqn", "com/itsaky/androidide/indexing/TestClass")
|
||||
.findFirst()
|
||||
|
||||
assertThat(dbKlass).isNotNull()
|
||||
assertThat(dbKlass?.fqn).isEqualTo("com/itsaky/androidide/indexing/TestClass")
|
||||
assertThat(dbKlass?.name).isEqualTo("TestClass")
|
||||
assertThat(dbKlass?.packageName).isEqualTo("com/itsaky/androidide/indexing")
|
||||
assertThat(dbKlass?.accessFlags).isEqualTo(AccessFlags.ACC_PUBLIC or AccessFlags.ACC_FINAL)
|
||||
assertThat(dbKlass?.superClassFqn).isEqualTo("java/lang/Object")
|
||||
assertThat(dbKlass?.superInterfacesFqn).isEqualTo(RealmList<String>())
|
||||
assertThat(dbKlass?.fields).isNotEmpty()
|
||||
assertThat(dbKlass?.fields).hasSize(1)
|
||||
|
||||
dbKlass?.fields?.get(0).apply {
|
||||
assertThat(this).isNotNull()
|
||||
assertThat(this?.name).isEqualTo("someString")
|
||||
assertThat(this?.type).isEqualTo(JavaType.STRING)
|
||||
assertThat(this?.accessFlags).isEqualTo(AccessFlags.ACC_PRIVATE)
|
||||
}
|
||||
|
||||
assertThat(dbKlass?.methods).isNotEmpty()
|
||||
assertThat(dbKlass?.methods).hasSize(1)
|
||||
|
||||
dbKlass?.methods?.get(0).apply {
|
||||
assertThat(this).isNotNull()
|
||||
assertThat(this?.name).isEqualTo("getSomeString")
|
||||
assertThat(this?.paramsTypes).isEqualTo(RealmList<String>())
|
||||
assertThat(this?.returnType).isEqualTo(JavaType.STRING)
|
||||
assertThat(this?.accessFlags).isEqualTo(AccessFlags.ACC_PUBLIC)
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(dbKlass).isNotNull()
|
||||
|
||||
// UPDATE
|
||||
executeTransaction {
|
||||
dbKlass?.accessFlags = AccessFlags.ACC_PUBLIC or AccessFlags.ACC_ABSTRACT
|
||||
}
|
||||
|
||||
// verify UPDATE
|
||||
executeTransaction {
|
||||
assertThat(
|
||||
where(JavaClass::class.java)
|
||||
.equalTo("fqn", "com/itsaky/androidide/indexing/TestClass")
|
||||
.findFirst()?.accessFlags
|
||||
).isEqualTo(AccessFlags.ACC_PUBLIC or AccessFlags.ACC_ABSTRACT)
|
||||
}
|
||||
|
||||
// DELETE
|
||||
executeTransaction {
|
||||
RealmObject.deleteFromRealm(dbKlass!!)
|
||||
}
|
||||
|
||||
// verify DELTE
|
||||
executeTransaction {
|
||||
assertThat(
|
||||
where(JavaClass::class.java)
|
||||
.equalTo("fqn", "com/itsaky/androidide/indexing/TestClass")
|
||||
.findFirst()
|
||||
).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.blankj.utilcode.util.ConvertUtils
|
||||
import com.google.common.truth.Truth
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaIndexingRealmModule
|
||||
import com.itsaky.androidide.testing.android.rules.RealmDBTestRule
|
||||
import io.realm.RealmModel
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class JavaIndexerTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val dbTestRule = RealmDBTestRule(JavaIndexingRealmModule())
|
||||
|
||||
val androidJar: File by lazy {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val file = File.createTempFile("ajar", null, context.cacheDir)
|
||||
context.assets.open("android.jar").use { asset ->
|
||||
Truth.assertThat(asset).isNotNull()
|
||||
file.outputStream().buffered().use { out ->
|
||||
asset.copyTo(out)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
file
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleAndroidJarIndexingDurationCheck() {
|
||||
val worker = JavaJarModelBuilder(androidJar)
|
||||
val batches = mutableMapOf<Class<*>, MutableList<RealmModel>>()
|
||||
|
||||
var dbWriteDuration = 0L
|
||||
|
||||
dbTestRule.withDb("android-jar-classes") {
|
||||
val totalStart = System.currentTimeMillis()
|
||||
worker.consumeTypes { type ->
|
||||
val batched = batches.computeIfAbsent(type.javaClass) { mutableListOf() }
|
||||
batched.add(type)
|
||||
|
||||
if (batched.size >= 100) {
|
||||
val start = System.currentTimeMillis()
|
||||
executeTransaction {
|
||||
insertOrUpdate(batched)
|
||||
}
|
||||
dbWriteDuration += System.currentTimeMillis() - start
|
||||
|
||||
batched.clear()
|
||||
}
|
||||
}
|
||||
|
||||
for ((_, batched) in batches) {
|
||||
if (batched.isNotEmpty()) {
|
||||
val start = System.currentTimeMillis()
|
||||
executeTransaction {
|
||||
insertOrUpdate(batched)
|
||||
}
|
||||
dbWriteDuration += System.currentTimeMillis() - start
|
||||
}
|
||||
}
|
||||
|
||||
println(
|
||||
"Took ${dbWriteDuration}ms to write android.jar (${
|
||||
ConvertUtils.byte2FitMemorySize(
|
||||
androidJar.length()
|
||||
)
|
||||
}) classes to Realm"
|
||||
)
|
||||
println("Total time: ${System.currentTimeMillis() - totalStart}ms")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaField
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaMethod
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
|
||||
import io.realm.RealmList
|
||||
import openjdk.tools.classfile.AccessFlags
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object ModelBuilder {
|
||||
|
||||
fun createJavaClass() = JavaClass.newInstance(
|
||||
fqn = "com/itsaky/androidide/indexing/TestClass",
|
||||
name = "TestClass",
|
||||
packageName = "com/itsaky/androidide/indexing",
|
||||
accessFlags = AccessFlags.ACC_PUBLIC or AccessFlags.ACC_FINAL,
|
||||
superClassFqn = "java/lang/Object",
|
||||
superInterfacesFqn = RealmList(),
|
||||
fields = RealmList<JavaField>().apply {
|
||||
add(JavaField.newField("someString", JavaType.STRING, AccessFlags.ACC_PRIVATE))
|
||||
},
|
||||
methods = RealmList<JavaMethod>().apply {
|
||||
add(
|
||||
JavaMethod.newInstance(
|
||||
name = "getSomeString",
|
||||
paramsTypes = RealmList(),
|
||||
returnType = JavaType.STRING,
|
||||
accessFlags = AccessFlags.ACC_PUBLIC
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.lsp.java.indexing.apiinfo.ApiInfo
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.ArrayAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.ClassAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.EnumAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.IAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaConstant
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaIndexingRealmModule
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.PrimitiveAnnotationElementValue
|
||||
import com.itsaky.androidide.testing.android.rules.RealmDBTestRule
|
||||
import io.realm.RealmAny
|
||||
import io.realm.RealmList
|
||||
import io.realm.exceptions.RealmPrimaryKeyConstraintException
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SharedModelCRUDTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val dbTestRule = RealmDBTestRule(JavaIndexingRealmModule())
|
||||
|
||||
@Test
|
||||
fun testApiInfoCreate() {
|
||||
dbTestRule.withDb("api-info") {
|
||||
val apiInfo = ApiInfo.newInstance(since = 1, deprecatedIn = 21, removedIn = 23)
|
||||
assertInsertUnique(apiInfo)
|
||||
|
||||
val apiInfo2 = apiInfo.clone().update(removedIn = 26)
|
||||
assertInsertion(allowUpdate = true, apiInfo, apiInfo2) { models ->
|
||||
assertThat(models).hasSize(2)
|
||||
assertThat(models).containsExactly(apiInfo, apiInfo2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testApiInfoDuplicationError() {
|
||||
dbTestRule.withDb("api-info-duplication-error") {
|
||||
val apiInfo = ApiInfo.newInstance(since = 1, deprecatedIn = 21, removedIn = 23)
|
||||
assertInsertSingle(apiInfo)
|
||||
assertInsertSingle(apiInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConstantCreate() {
|
||||
dbTestRule.withDb("java-constant-create") {
|
||||
val constant =
|
||||
JavaConstant.newInstance(kind = JavaType.KIND_INT, value = RealmAny.valueOf(1.toInt()))
|
||||
val constant2 =
|
||||
JavaConstant.newInstance(kind = JavaType.KIND_INT, value = RealmAny.valueOf(2.toInt()))
|
||||
|
||||
assertInsertUnique(constant)
|
||||
|
||||
assertInsertion(allowUpdate = true, constant, constant2) { models ->
|
||||
assertThat(models).hasSize(2)
|
||||
assertThat(models).containsExactly(constant, constant2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testConstantDuplicationError() {
|
||||
dbTestRule.withDb("java-constant-duplication-error") {
|
||||
val constant =
|
||||
JavaConstant.newInstance(kind = JavaType.KIND_INT, value = RealmAny.valueOf(1.toInt()))
|
||||
assertInsertSingle(constant)
|
||||
assertInsertSingle(constant)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJavaTypeCreate() {
|
||||
dbTestRule.withDb("java-type-create") {
|
||||
assertInsertUnique(JavaType.INT)
|
||||
|
||||
assertInsertion(JavaType.STRING, JavaType.OBJECT) { models ->
|
||||
assertThat(models).hasSize(3)
|
||||
assertThat(models).containsExactly(JavaType.INT, JavaType.STRING, JavaType.OBJECT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testJavaTypeDuplicationError() {
|
||||
dbTestRule.withDb("java-type-duplication-error") {
|
||||
assertInsertSingle(JavaType.OBJECT)
|
||||
assertInsertSingle(JavaType.OBJECT)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrimitiveAnnotationValueCreate() {
|
||||
dbTestRule.withDb("annotation-value-primitive") {
|
||||
val prim1 = PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_INT,
|
||||
JavaConstant.newInstance(JavaType.KIND_INT, RealmAny.valueOf(1.toInt()))
|
||||
)
|
||||
val prim2 = PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_STRING,
|
||||
JavaConstant.newInstance(JavaType.KIND_REF, RealmAny.valueOf("Something something"))
|
||||
)
|
||||
|
||||
assertInsertUnique(prim1)
|
||||
|
||||
assertInsertion(allowUpdate = true, prim1, prim2) { models ->
|
||||
assertThat(models).hasSize(2)
|
||||
assertThat(models).containsExactly(prim1, prim2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testPrimitiveAnnotationValueDuplicationError() {
|
||||
dbTestRule.withDb("annotation-value-primitive-duplication") {
|
||||
val prim1 = PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_INT,
|
||||
JavaConstant.newInstance(JavaType.KIND_INT, RealmAny.valueOf(1.toInt()))
|
||||
)
|
||||
assertInsertSingle(prim1)
|
||||
assertInsertSingle(prim1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArrayAnnotationValueCreate() {
|
||||
dbTestRule.withDb("annotation-value-array") {
|
||||
val obj1 = ArrayAnnotationElementValue.newInstance(
|
||||
RealmList()
|
||||
)
|
||||
val obj2 = ArrayAnnotationElementValue.newInstance(
|
||||
RealmList<RealmAny>().apply {
|
||||
add(
|
||||
RealmAny.valueOf(
|
||||
PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_STRING,
|
||||
JavaConstant.newInstance(JavaType.KIND_REF, RealmAny.valueOf("Something something"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
RealmAny.valueOf(
|
||||
PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_INT,
|
||||
JavaConstant.newInstance(JavaType.KIND_INT, RealmAny.valueOf(123.toInt()))
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
assertInsertUnique(obj1)
|
||||
|
||||
assertInsertion(allowUpdate = true, obj1, obj2) { models ->
|
||||
assertThat(models).hasSize(2)
|
||||
assertThat(models).containsExactly(obj1, obj2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testArrayAnnotationValueDuplicationError() {
|
||||
dbTestRule.withDb("annotation-value-array-duplication") {
|
||||
val obj1 = ArrayAnnotationElementValue.newInstance(
|
||||
RealmList<RealmAny>().apply {
|
||||
add(
|
||||
RealmAny.valueOf(
|
||||
PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_STRING,
|
||||
JavaConstant.newInstance(JavaType.KIND_REF, RealmAny.valueOf("Something something"))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
RealmAny.valueOf(
|
||||
PrimitiveAnnotationElementValue.newInstance(
|
||||
IAnnotationElementValue.KIND_INT,
|
||||
JavaConstant.newInstance(JavaType.KIND_INT, RealmAny.valueOf(123.toInt()))
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
assertInsertSingle(obj1)
|
||||
assertInsertSingle(obj1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassAnnotationValueCreate() {
|
||||
dbTestRule.withDb("annotation-value-class") {
|
||||
val obj1 = ClassAnnotationElementValue.newInstance(
|
||||
JavaType.newInstance("com/example/SomeType", JavaType.KIND_REF)
|
||||
)
|
||||
val obj2 = ClassAnnotationElementValue.newInstance(
|
||||
JavaType.newInstance("com/example/SomeType", JavaType.KIND_REF, arrayDims = 5)
|
||||
)
|
||||
|
||||
assertInsertUnique(obj1)
|
||||
|
||||
assertInsertion(allowUpdate = true, obj1, obj2) { models ->
|
||||
assertThat(models).hasSize(2)
|
||||
assertThat(models).containsExactly(obj1, obj2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testClassAnnotationValueDuplicationError() {
|
||||
dbTestRule.withDb("annotation-value-class-duplication") {
|
||||
val obj1 = ClassAnnotationElementValue.newInstance(
|
||||
JavaType.newInstance("com/example/SomeType", JavaType.KIND_REF)
|
||||
)
|
||||
assertInsertSingle(obj1)
|
||||
assertInsertSingle(obj1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnumAnnotationValueCreate() {
|
||||
dbTestRule.withDb("annotation-value-enum") {
|
||||
val obj1 = EnumAnnotationElementValue.newInstance(
|
||||
"SomeEnum_ENTRY",
|
||||
JavaType.newInstance("com/example/SomeEnum", JavaType.KIND_REF),
|
||||
)
|
||||
val obj2 = EnumAnnotationElementValue.newInstance(
|
||||
"SomeEnum_ENTRY_2",
|
||||
JavaType.newInstance("com/example/SomeEnum", JavaType.KIND_REF),
|
||||
)
|
||||
|
||||
assertInsertUnique(obj1)
|
||||
|
||||
assertInsertion(allowUpdate = true, obj1, obj2) { models ->
|
||||
assertThat(models).hasSize(2)
|
||||
assertThat(models).containsExactly(obj1, obj2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RealmPrimaryKeyConstraintException::class)
|
||||
fun testEnumAnnotationValueDuplicationError() {
|
||||
dbTestRule.withDb("annotation-value-enum-duplication") {
|
||||
val obj1 = EnumAnnotationElementValue.newInstance(
|
||||
"SomeEnum_ENTRY",
|
||||
JavaType.newInstance("com/example/SomeEnum", JavaType.KIND_REF),
|
||||
)
|
||||
assertInsertSingle(obj1)
|
||||
assertInsertSingle(obj1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmModel
|
||||
import io.realm.RealmResults
|
||||
|
||||
inline fun <reified T : RealmModel> Realm.assertInsertion(
|
||||
vararg models: T,
|
||||
crossinline verify: Realm.(models: RealmResults<T>) -> Unit
|
||||
) {
|
||||
assertInsertion(false, *models, verify = verify)
|
||||
}
|
||||
|
||||
inline fun <reified T : RealmModel> Realm.assertInsertion(
|
||||
allowUpdate: Boolean = false,
|
||||
vararg models: T,
|
||||
crossinline verify: Realm.(models: RealmResults<T>) -> Unit
|
||||
) {
|
||||
val modelList = models.toList()
|
||||
assertThat(modelList).isNotEmpty()
|
||||
|
||||
executeTransaction {
|
||||
if (allowUpdate) {
|
||||
it.insertOrUpdate(modelList)
|
||||
} else {
|
||||
it.insert(modelList)
|
||||
}
|
||||
}
|
||||
|
||||
executeTransaction {
|
||||
verify(it.where(modelList[0].javaClass).findAll())
|
||||
}
|
||||
}
|
||||
|
||||
fun Realm.assertInsertSingle(model: RealmModel) {
|
||||
assertInsertion(model) { models ->
|
||||
assertThat(models).isNotNull()
|
||||
assertThat(models).isNotEmpty()
|
||||
assertThat(models).hasSize(1)
|
||||
assertThat(models[0]).isEqualTo(model)
|
||||
}
|
||||
}
|
||||
|
||||
fun Realm.assertInsertUnique(model: RealmModel) {
|
||||
assertInsertSingle(model)
|
||||
assertInsertion(true, model) { models ->
|
||||
assertThat(models).isNotNull()
|
||||
assertThat(models).isNotEmpty()
|
||||
assertThat(models).hasSize(1)
|
||||
assertThat(models[0]).isEqualTo(model)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ This file is part of AndroidIDE.
|
||||
~
|
||||
~ AndroidIDE is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ AndroidIDE is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<manifest/>
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"lcomm": {
|
||||
"desc": "Create a line comment",
|
||||
"body": [
|
||||
"// $0"
|
||||
]
|
||||
},
|
||||
"bcomm": {
|
||||
"desc": "Create a block comment",
|
||||
"body": [
|
||||
"/*",
|
||||
" * $0",
|
||||
" */"
|
||||
]
|
||||
},
|
||||
"todo": {
|
||||
"desc": "Create a TODO comment",
|
||||
"body": [
|
||||
"// TODO: $0"
|
||||
]
|
||||
},
|
||||
"fixme": {
|
||||
"desc": "Create a FIXME comment",
|
||||
"body": [
|
||||
"// FIXME: $0"
|
||||
]
|
||||
},
|
||||
"jdoc": {
|
||||
"desc": "Create a JavaDoc comment",
|
||||
"body": [
|
||||
"/**",
|
||||
" * $0",
|
||||
" */"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"for": {
|
||||
"desc": "Indexed for loop",
|
||||
"body": [
|
||||
"for(int ${1:i} = 0; $1 < ${2:count}; ++$1) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"foreach": {
|
||||
"desc": "For-each loop",
|
||||
"body": [
|
||||
"for(${1:element} : ${2:iterable}) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"forr": {
|
||||
"desc": "Reverse-indexed for loop",
|
||||
"body": [
|
||||
"for(int ${1:i} = ${2:count} - 1; $1 >= 0; --$1) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"while": {
|
||||
"desc": "While loop",
|
||||
"body": [
|
||||
"while(${1:condition}) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"dowhile": {
|
||||
"desc": "Do-While loop",
|
||||
"body": [
|
||||
"do {",
|
||||
"\t$0",
|
||||
"} while(${1:condition});"
|
||||
]
|
||||
},
|
||||
"if": {
|
||||
"desc": "If statement",
|
||||
"body": [
|
||||
"if(${1:condition}) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"ifelse": {
|
||||
"desc": "If-else statement",
|
||||
"body": [
|
||||
"if(${1:condition}) {",
|
||||
"\t$0",
|
||||
"} else {",
|
||||
"\t",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"ifnull": {
|
||||
"desc": "If-null",
|
||||
"body": [
|
||||
"if(${1:object} == null) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"ifnotnull": {
|
||||
"desc": "If-not-null",
|
||||
"body": [
|
||||
"if(${1:object} != null) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"new_object": {
|
||||
"desc": "Create a new object",
|
||||
"body": [
|
||||
"${1:Object} ${2:foo} = new $1($3);",
|
||||
"$0"
|
||||
]
|
||||
},
|
||||
"trycatch": {
|
||||
"desc": "A try-catch statement",
|
||||
"body": [
|
||||
"try {",
|
||||
"\t$0",
|
||||
"} catch(${1:Exception} ${2:err}) {",
|
||||
"\t",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"tryresources": {
|
||||
"desc": "A try-with-resources statement",
|
||||
"body": [
|
||||
"try ($1) {",
|
||||
"\t$0",
|
||||
"} catch(${2:Exception} ${3:err}) {",
|
||||
"\t",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"const": {
|
||||
"desc": "Create a constant field",
|
||||
"body": [
|
||||
"public static final ${1:type} ${2/(.*)/${1:/upcase}/} = ${3:value};"
|
||||
]
|
||||
},
|
||||
"ctor": {
|
||||
"desc": "Public constructor",
|
||||
"body": [
|
||||
"public ${1:$TM_FILENAME_BASE}($2) {",
|
||||
"\t${3:super();}$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"public_method": {
|
||||
"desc": "Create a new public method",
|
||||
"body": [
|
||||
"public ${1:void} ${2:method}($3) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"protected_method": {
|
||||
"desc": "Create a new protected method",
|
||||
"body": [
|
||||
"protected ${1:void} ${2:method}($3) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"private_method": {
|
||||
"desc": "Create a new private method",
|
||||
"body": [
|
||||
"private ${1:void} ${2:method}($3) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"public_static_method": {
|
||||
"desc": "Create a new public static method",
|
||||
"body": [
|
||||
"public static ${1:void} ${2:method}($3) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"private_static_method": {
|
||||
"desc": "Create a new private stataic method",
|
||||
"body": [
|
||||
"private static ${1:void} ${2:method}($3) {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"class": {
|
||||
"desc": "Create a new class",
|
||||
"body" : [
|
||||
"public class ${1:$TM_FILENAME_BASE} {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"interface": {
|
||||
"desc": "Create a new interface",
|
||||
"body" : [
|
||||
"public interface ${1:$TM_FILENAME_BASE} {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"enum": {
|
||||
"desc": "Create a new enum class",
|
||||
"body" : [
|
||||
"public enum ${1:$TM_FILENAME_BASE} {",
|
||||
"\t$0",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java
|
||||
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
/**
|
||||
* Thrown when a compilation process is cancelled.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CompilationCancellationException @JvmOverloads constructor(
|
||||
override val cause: Throwable? = null
|
||||
) : CancellationException()
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Provides {@link JavaCompilerService} instances for different {@link ModuleProject}s.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class JavaCompilerProvider {
|
||||
|
||||
private static JavaCompilerProvider sInstance;
|
||||
private final Map<ModuleProject, JavaCompilerService> mCompilers = new ConcurrentHashMap<>();
|
||||
|
||||
private JavaCompilerProvider() {}
|
||||
|
||||
@NonNull
|
||||
public static JavaCompilerService get(ModuleProject module) {
|
||||
return JavaCompilerProvider.getInstance().forModule(module);
|
||||
}
|
||||
|
||||
public static JavaCompilerProvider getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new JavaCompilerProvider();
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized JavaCompilerService forModule(ModuleProject module) {
|
||||
// A module instance is set to the compiler only in case the project is initialized or
|
||||
// this method was called with other mdoule instance.
|
||||
final JavaCompilerService cached = mCompilers.get(module);
|
||||
if (cached != null && cached.getModule() != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final JavaCompilerService newInstance = new JavaCompilerService(module);
|
||||
mCompilers.put(module, newInstance);
|
||||
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
// TODO This currently destroys all the compiler instances
|
||||
// We must have a method to destroy only the required instance in
|
||||
// JavaLanguageServer.handleFailure(LSPFailure)
|
||||
public synchronized void destroy() {
|
||||
for (final JavaCompilerService compiler : mCompilers.values()) {
|
||||
compiler.destroy();
|
||||
}
|
||||
mCompilers.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java
|
||||
|
||||
import androidx.annotation.RestrictTo
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentCloseEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentOpenEvent
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentSelectedEvent
|
||||
import com.itsaky.androidide.javac.services.fs.CacheFSInfoSingleton
|
||||
import com.itsaky.androidide.javac.services.fs.CachingJarFileSystemProvider.clearCache
|
||||
import com.itsaky.androidide.javac.services.fs.CachingJarFileSystemProvider.clearCachesForPaths
|
||||
import com.itsaky.androidide.lsp.api.ILanguageClient
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServer
|
||||
import com.itsaky.androidide.lsp.api.IServerSettings
|
||||
import com.itsaky.androidide.lsp.internal.model.CachedCompletion
|
||||
import com.itsaky.androidide.lsp.java.actions.JavaCodeActionsMenu
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.compiler.SourceFileManager
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerSettings
|
||||
import com.itsaky.androidide.lsp.java.providers.CodeFormatProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.CompletionProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.DefinitionProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.JavaDiagnosticProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.JavaSelectionProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.ReferenceProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.SignatureProvider
|
||||
import com.itsaky.androidide.lsp.java.providers.snippet.JavaSnippetRepository.init
|
||||
import com.itsaky.androidide.lsp.java.utils.AnalyzeTimer
|
||||
import com.itsaky.androidide.lsp.java.utils.CancelChecker.Companion.isCancelled
|
||||
import com.itsaky.androidide.lsp.models.CodeFormatResult
|
||||
import com.itsaky.androidide.lsp.models.CompletionParams
|
||||
import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.DefinitionParams
|
||||
import com.itsaky.androidide.lsp.models.DefinitionResult
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticResult
|
||||
import com.itsaky.androidide.lsp.models.ExpandSelectionParams
|
||||
import com.itsaky.androidide.lsp.models.FailureType
|
||||
import com.itsaky.androidide.lsp.models.FormatCodeParams
|
||||
import com.itsaky.androidide.lsp.models.LSPFailure
|
||||
import com.itsaky.androidide.lsp.models.ReferenceParams
|
||||
import com.itsaky.androidide.lsp.models.ReferenceResult
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelp
|
||||
import com.itsaky.androidide.lsp.models.SignatureHelpParams
|
||||
import com.itsaky.androidide.lsp.util.LSPEditorActions
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.projects.FileManager.getActiveDocumentCount
|
||||
import com.itsaky.androidide.projects.IProjectManager.Companion.getInstance
|
||||
import com.itsaky.androidide.projects.api.ModuleProject
|
||||
import com.itsaky.androidide.projects.api.Project
|
||||
import com.itsaky.androidide.utils.DocumentUtils
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.util.Objects
|
||||
|
||||
class JavaLanguageServer : ILanguageServer {
|
||||
|
||||
private val completionProvider: CompletionProvider = CompletionProvider()
|
||||
private val diagnosticProvider: JavaDiagnosticProvider?
|
||||
override var client: ILanguageClient? = null
|
||||
private set
|
||||
|
||||
private var _settings: IServerSettings? = null
|
||||
private var selectedFile: Path? = null
|
||||
private val timer = AnalyzeTimer { analyzeSelected() }
|
||||
private var cachedCompletion: CachedCompletion
|
||||
|
||||
val settings: IServerSettings
|
||||
get() {
|
||||
return _settings ?: JavaServerSettings.getInstance()
|
||||
.also { _settings = it }
|
||||
}
|
||||
|
||||
override val serverId: String = SERVER_ID
|
||||
|
||||
companion object {
|
||||
|
||||
const val SERVER_ID = "ide.lsp.java"
|
||||
private val log = LoggerFactory.getLogger(JavaLanguageServer::class.java)
|
||||
}
|
||||
|
||||
init {
|
||||
diagnosticProvider = JavaDiagnosticProvider()
|
||||
cachedCompletion = CachedCompletion.EMPTY
|
||||
|
||||
applySettings(JavaServerSettings.getInstance())
|
||||
|
||||
if (!EventBus.getDefault().isRegistered(this)) {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
JavaCompilerProvider.getInstance().destroy()
|
||||
SourceFileManager.clearCache()
|
||||
CacheFSInfoSingleton.clearCache()
|
||||
clearCache()
|
||||
EventBus.getDefault().unregister(this)
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
override fun connectClient(client: ILanguageClient?) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
override fun applySettings(settings: IServerSettings?) {
|
||||
this._settings = settings
|
||||
}
|
||||
|
||||
override fun setupWithProject(project: Project) {
|
||||
LSPEditorActions.ensureActionsMenuRegistered(JavaCodeActionsMenu)
|
||||
|
||||
// Once we have project initialized
|
||||
// Destory the NO_MODULE_COMPILER instance
|
||||
JavaCompilerService.NO_MODULE_COMPILER.destroy()
|
||||
|
||||
// Clear cached file managers
|
||||
SourceFileManager.clearCache()
|
||||
|
||||
// Clear cached JAR file system for R.jar
|
||||
// Using the cached instance will result in completions not being updated for updated resources
|
||||
// TODO Clearing caches for JAR files ending with '/R.jar' is probably not a good idea
|
||||
// Maybe this could be improved by using data from the AndroidModule project model
|
||||
clearCachesForPaths { path: String -> path.endsWith("/R.jar") }
|
||||
|
||||
// Clear cached module-specific compilers
|
||||
JavaCompilerProvider.getInstance().destroy()
|
||||
|
||||
// Cache classpath locations
|
||||
for (subModule in project.subProjects) {
|
||||
if (subModule !is ModuleProject || subModule.path == project.rootProject.path) {
|
||||
continue
|
||||
}
|
||||
SourceFileManager.forModule(subModule)
|
||||
}
|
||||
startOrRestartAnalyzeTimer()
|
||||
}
|
||||
|
||||
override fun complete(params: CompletionParams?): CompletionResult {
|
||||
val compiler = getCompiler(params!!.file)
|
||||
if (!settings.completionsEnabled() || !completionProvider.canComplete(params.file)
|
||||
) {
|
||||
return CompletionResult.EMPTY
|
||||
}
|
||||
|
||||
if (diagnosticProvider!!.isAnalyzing()) {
|
||||
log.warn("Cancelling source code analysis due to completion request")
|
||||
diagnosticProvider.cancel()
|
||||
}
|
||||
|
||||
completionProvider.reset(
|
||||
compiler, settings, cachedCompletion) { cachedCompletion: CachedCompletion ->
|
||||
updateCachedCompletion(cachedCompletion)
|
||||
}
|
||||
|
||||
return completionProvider.complete(params)
|
||||
}
|
||||
|
||||
override suspend fun findReferences(params: ReferenceParams): ReferenceResult {
|
||||
val compiler = getCompiler(params.file)
|
||||
return if (!settings.referencesEnabled()) {
|
||||
ReferenceResult(emptyList())
|
||||
} else ReferenceProvider(compiler, params.cancelChecker).findReferences(params)
|
||||
}
|
||||
|
||||
override suspend fun findDefinition(params: DefinitionParams): DefinitionResult {
|
||||
val compiler = getCompiler(params.file)
|
||||
return if (!settings.definitionsEnabled()) {
|
||||
DefinitionResult(emptyList())
|
||||
} else DefinitionProvider(compiler, settings, params.cancelChecker).findDefinition(params)
|
||||
}
|
||||
|
||||
override suspend fun expandSelection(params: ExpandSelectionParams): Range {
|
||||
val compiler = getCompiler(params.file)
|
||||
return if (!settings.smartSelectionsEnabled()) {
|
||||
params.selection
|
||||
} else JavaSelectionProvider(compiler).expandSelection(params)
|
||||
}
|
||||
|
||||
override suspend fun signatureHelp(params: SignatureHelpParams): SignatureHelp {
|
||||
val compiler = getCompiler(params.file)
|
||||
return if (!settings.signatureHelpEnabled()) {
|
||||
SignatureHelp(emptyList(), -1, -1)
|
||||
} else SignatureProvider(compiler, params.cancelChecker).signatureHelp(params)
|
||||
}
|
||||
|
||||
override suspend fun analyze(file: Path): DiagnosticResult {
|
||||
if (!settings.diagnosticsEnabled() || !DocumentUtils.isJavaFile(file)) {
|
||||
return DiagnosticResult.NO_UPDATE
|
||||
}
|
||||
|
||||
return if (!settings.codeAnalysisEnabled()) {
|
||||
DiagnosticResult.NO_UPDATE
|
||||
} else diagnosticProvider!!.analyze(file)
|
||||
}
|
||||
|
||||
override fun formatCode(params: FormatCodeParams?): CodeFormatResult {
|
||||
return CodeFormatProvider(settings).format(params)
|
||||
}
|
||||
|
||||
override fun handleFailure(failure: LSPFailure?): Boolean {
|
||||
return when (failure!!.type) {
|
||||
FailureType.COMPLETION -> {
|
||||
if (isCancelled(failure.error)) {
|
||||
return true
|
||||
}
|
||||
JavaCompilerProvider.getInstance().destroy()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
|
||||
fun getCompiler(file: Path?): JavaCompilerService {
|
||||
if (!DocumentUtils.isJavaFile(file)) {
|
||||
return JavaCompilerService.NO_MODULE_COMPILER
|
||||
}
|
||||
val root = getInstance().rootProject
|
||||
?: return JavaCompilerService.NO_MODULE_COMPILER
|
||||
val module = root.findModuleForFile(file!!) ?: return JavaCompilerService.NO_MODULE_COMPILER
|
||||
return JavaCompilerProvider.get(module)
|
||||
}
|
||||
|
||||
private fun updateCachedCompletion(cachedCompletion: CachedCompletion) {
|
||||
Objects.requireNonNull(cachedCompletion)
|
||||
this.cachedCompletion = cachedCompletion
|
||||
}
|
||||
|
||||
private fun startOrRestartAnalyzeTimer() {
|
||||
if (VMUtils.isJvm()) {
|
||||
return
|
||||
}
|
||||
if (!timer.isStarted) {
|
||||
timer.start()
|
||||
} else {
|
||||
timer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||
@Suppress("unused")
|
||||
fun onContentChange(event: DocumentChangeEvent) {
|
||||
if (!DocumentUtils.isJavaFile(event.changedFile)) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance
|
||||
JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event)
|
||||
val module = getInstance()
|
||||
.findModuleForFile(event.changedFile)
|
||||
if (module != null) {
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
compiler.onDocumentChange(event)
|
||||
}
|
||||
startOrRestartAnalyzeTimer()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||
@Suppress("unused")
|
||||
fun onFileSelected(event: DocumentSelectedEvent) {
|
||||
selectedFile = event.selectedFile
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||
@Suppress("unused")
|
||||
fun onFileOpened(event: DocumentOpenEvent) {
|
||||
selectedFile = event.openedFile
|
||||
startOrRestartAnalyzeTimer()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||
@Suppress("unused")
|
||||
fun onFileClosed(event: DocumentCloseEvent) {
|
||||
diagnosticProvider?.clearTimestamp(event.closedFile)
|
||||
|
||||
if (getActiveDocumentCount() == 0) {
|
||||
selectedFile = null
|
||||
timer.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeSelected() {
|
||||
if (selectedFile == null || client == null) {
|
||||
return
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val result = analyze(selectedFile!!)
|
||||
withContext(Dispatchers.Main) {
|
||||
client?.publishDiagnostics(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.EditorActionItem
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.lsp.api.ILanguageClient
|
||||
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.utils.DocumentUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Base class for java code actions
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class BaseJavaCodeAction : EditorActionItem {
|
||||
|
||||
override var visible: Boolean = true
|
||||
override var enabled: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var requiresUIThread: Boolean = false
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_CODE_ACTIONS
|
||||
|
||||
protected abstract val titleTextRes: Int
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
if (
|
||||
!data.hasRequiredData(Context::class.java, JavaLanguageServer::class.java, File::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
if (titleTextRes != -1) {
|
||||
label = data[Context::class.java]!!.getString(titleTextRes)
|
||||
}
|
||||
|
||||
val file = data.requireFile()
|
||||
visible = DocumentUtils.isJavaFile(file.toPath())
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
fun performCodeAction(data: ActionData, result: Rewrite) {
|
||||
val compiler = data.requireCompiler()
|
||||
|
||||
val actions =
|
||||
try {
|
||||
result.asCodeActions(compiler, label)
|
||||
} catch (e: Exception) {
|
||||
flashError(e.cause?.message ?: e.message)
|
||||
ILogger.ROOT.error(e.cause?.message ?: e.message, e)
|
||||
return
|
||||
}
|
||||
|
||||
if (actions == null) {
|
||||
onPerformCodeActionFailed(data)
|
||||
return
|
||||
}
|
||||
|
||||
data.getLanguageClient()?.performCodeAction(actions)
|
||||
}
|
||||
|
||||
protected open fun onPerformCodeActionFailed(data: ActionData) {
|
||||
flashError(R.string.msg_codeaction_failed)
|
||||
}
|
||||
|
||||
protected fun ActionData.requireLanguageServer(): JavaLanguageServer {
|
||||
return ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)
|
||||
as JavaLanguageServer
|
||||
}
|
||||
|
||||
protected fun ActionData.getLanguageClient(): ILanguageClient? {
|
||||
return requireLanguageServer().client
|
||||
}
|
||||
|
||||
protected fun ActionData.requireCompiler(): JavaCompilerService {
|
||||
val module = IProjectManager.getInstance().findModuleForFile(requireFile(), false)
|
||||
requireNotNull(module) {
|
||||
"Cannot get compiler instance. Unable to find module for file: ${requireFile().name}"
|
||||
}
|
||||
return JavaCompilerProvider.get(module)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.actions
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.newDialogBuilder
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import jdkx.lang.model.element.Modifier.STATIC
|
||||
import openjdk.source.tree.ClassTree
|
||||
import openjdk.source.tree.Tree.Kind.VARIABLE
|
||||
import openjdk.source.tree.VariableTree
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CompletionException
|
||||
|
||||
/**
|
||||
* Any action that has to work with fields in the current class can inherit this action.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class FieldBasedAction : BaseJavaCodeAction() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(FieldBasedAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (
|
||||
!visible ||
|
||||
!data.hasRequiredData(
|
||||
com.itsaky.androidide.models.Range::class.java,
|
||||
CodeEditor::class.java
|
||||
) ||
|
||||
IProjectManager.getInstance().rootProject == null
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = data.requirePath()
|
||||
val module = IProjectManager.getInstance().findModuleForFile(file, false) ?: return Any()
|
||||
|
||||
return JavaCompilerProvider.get(module).compile(file).get { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val type = triple.second
|
||||
val fields = triple.third
|
||||
val fieldNames = fields.map { "${it.name}: ${it.type}" } // Get the names
|
||||
|
||||
log.debug("Found {} fields in class {}", fieldNames.size, type.simpleName)
|
||||
|
||||
return@get fieldNames
|
||||
}
|
||||
}
|
||||
|
||||
protected fun findFields(
|
||||
task: CompileTask,
|
||||
file: Path,
|
||||
range: com.itsaky.androidide.models.Range
|
||||
): Triple<FindTypeDeclarationAt, ClassTree, MutableList<VariableTree>> {
|
||||
// 1-based line and column index
|
||||
val startLine = range.start.line + 1
|
||||
val startColumn = range.start.column + 1
|
||||
val endLine = range.end.line + 1
|
||||
val endColumn = range.end.column + 1
|
||||
val lines = task.root().lineMap
|
||||
val start = lines.getPosition(startLine.toLong(), startColumn.toLong())
|
||||
val end = lines.getPosition(endLine.toLong(), endColumn.toLong())
|
||||
|
||||
if (start == (-1).toLong() || end == (-1).toLong()) {
|
||||
throw CompletionException(
|
||||
RuntimeException("Unable to find position for the given selection range")
|
||||
)
|
||||
}
|
||||
|
||||
val typeFinder = FindTypeDeclarationAt(task.task)
|
||||
var type = typeFinder.scan(task.root(file), start)
|
||||
if (type == null) {
|
||||
type = typeFinder.scan(task.root(file), end)
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
throw CompletionException(
|
||||
RuntimeException("Unable to find class declaration within cursor range")
|
||||
)
|
||||
}
|
||||
|
||||
val fields =
|
||||
type.members
|
||||
.filter { it.kind == VARIABLE }
|
||||
.map { it as VariableTree }
|
||||
.filter { !it.modifiers.flags.contains(STATIC) }
|
||||
.toMutableList()
|
||||
return Triple(typeFinder, type, fields)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is List<*>) {
|
||||
log.error("Unable to find fields in the current class")
|
||||
return
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
flashInfo(data[Context::class.java]!!.getString(R.string.msg_no_fields_found))
|
||||
return
|
||||
}
|
||||
|
||||
onGetFields(result as List<String>, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the fields of the current class are found. As this method is called inside
|
||||
* [postExec], the current thread is the UI thread.
|
||||
*/
|
||||
abstract fun onGetFields(fields: List<String>, data: ActionData)
|
||||
|
||||
/**
|
||||
* Shows the field selector dialog. Returns a [CompletableFuture] which is completed when the user
|
||||
* confirms the selected fields.
|
||||
*/
|
||||
protected fun showFieldSelector(
|
||||
fields: List<String>,
|
||||
data: ActionData,
|
||||
listener: OnFieldsSelectedListener?
|
||||
) {
|
||||
val names = fields.toTypedArray()
|
||||
val checkedNames = mutableSetOf<String>()
|
||||
val builder = newDialogBuilder(data)
|
||||
builder.setTitle(data[Context::class.java]!!.getString(R.string.msg_select_fields))
|
||||
builder.setMultiChoiceItems(names, BooleanArray(fields.size)) { _, which, checked ->
|
||||
checkedNames.apply {
|
||||
val item = names[which]
|
||||
if (checked) {
|
||||
add(item)
|
||||
} else {
|
||||
remove(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
|
||||
if (checkedNames.isEmpty()) {
|
||||
flashInfo(data[Context::class.java]!!.getString(R.string.msg_no_fields_selected))
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
listener?.onFieldsSelected(checkedNames)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.show()
|
||||
}
|
||||
|
||||
/** Listener to get callback when fields are selected by the user. */
|
||||
fun interface OnFieldsSelectedListener {
|
||||
|
||||
/**
|
||||
* Called when the user is done selecting fields.
|
||||
*
|
||||
* @param fields The selected field names.
|
||||
*/
|
||||
fun onFieldsSelected(fields: MutableSet<String>)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.actions
|
||||
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.lsp.actions.IActionsMenuProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.common.CommentAction
|
||||
import com.itsaky.androidide.lsp.java.actions.common.FindReferencesAction
|
||||
import com.itsaky.androidide.lsp.java.actions.common.GoToDefinitionAction
|
||||
import com.itsaky.androidide.lsp.java.actions.common.OrganizeImportsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.common.RemoveUnusedImportsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.common.UncommentAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.AddImportAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.AddThrowsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.AutoFixImportsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.CreateMissingMethodAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.FieldToBlockAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.ImplementAbstractMethodsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveClassAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveMethodAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveUnusedThrowsAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.SuppressUncheckedWarningAction
|
||||
import com.itsaky.androidide.lsp.java.actions.diagnostics.VariableToStatementAction
|
||||
import com.itsaky.androidide.lsp.java.actions.generators.GenerateConstructorAction
|
||||
import com.itsaky.androidide.lsp.java.actions.generators.GenerateMissingConstructorAction
|
||||
import com.itsaky.androidide.lsp.java.actions.generators.GenerateSettersAndGettersAction
|
||||
import com.itsaky.androidide.lsp.java.actions.generators.GenerateToStringMethodAction
|
||||
import com.itsaky.androidide.lsp.java.actions.generators.OverrideSuperclassMethodsAction
|
||||
|
||||
/**
|
||||
* Java code actions.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object JavaCodeActionsMenu : IActionsMenuProvider {
|
||||
|
||||
override val actions: List<ActionItem> =
|
||||
listOf(
|
||||
CommentAction(),
|
||||
UncommentAction(),
|
||||
GoToDefinitionAction(),
|
||||
FindReferencesAction(),
|
||||
AddImportAction(),
|
||||
AutoFixImportsAction(),
|
||||
ImplementAbstractMethodsAction(),
|
||||
VariableToStatementAction(),
|
||||
FieldToBlockAction(),
|
||||
RemoveClassAction(),
|
||||
RemoveMethodAction(),
|
||||
RemoveUnusedThrowsAction(),
|
||||
CreateMissingMethodAction(),
|
||||
SuppressUncheckedWarningAction(),
|
||||
AddThrowsAction(),
|
||||
GenerateSettersAndGettersAction(),
|
||||
OverrideSuperclassMethodsAction(),
|
||||
GenerateMissingConstructorAction(),
|
||||
GenerateConstructorAction(),
|
||||
GenerateToStringMethodAction(),
|
||||
RemoveUnusedImportsAction(),
|
||||
OrganizeImportsAction()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.common
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireEditor
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.resources.R
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CommentAction : BaseJavaCodeAction() {
|
||||
override val id: String = "ide.editor.lsp.java.commentLine"
|
||||
override var label: String = ""
|
||||
|
||||
override val titleTextRes: Int = R.string.action_comment_line
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override suspend fun execAction(data: ActionData): Boolean {
|
||||
val editor = data.requireEditor()
|
||||
val text = editor.text
|
||||
val cursor = editor.cursor
|
||||
|
||||
text.beginBatchEdit()
|
||||
for (line in cursor.leftLine..cursor.rightLine) {
|
||||
text.insert(line, 0, "//")
|
||||
}
|
||||
text.endBatchEdit()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun dismissOnAction() = false
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.common
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.editor.api.ILspEditor
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.resources.R
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Action that allows the user to find references to a variable, field, method or class.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class FindReferencesAction : BaseJavaCodeAction() {
|
||||
|
||||
override val titleTextRes: Int = R.string.action_find_references
|
||||
override val id: String = "ide.editor.lsp.java.findReferences"
|
||||
override var label: String = ""
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(CodeEditor::class.java, File::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
return (editor as? ILspEditor)?.findReferences() ?: false
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.common
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.editor.api.ILspEditor
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.resources.R
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Action that allows the user to navigate to the definition of a variable, field, method, class,
|
||||
* etc.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToDefinitionAction : BaseJavaCodeAction() {
|
||||
|
||||
override val titleTextRes: Int = R.string.action_goto_definition
|
||||
override val id: String = "ide.editor.lsp.java.gotoDefinition"
|
||||
override var label: String = ""
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(CodeEditor::class.java, File::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
return (editor as? ILspEditor)?.findDefinition() ?: false
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.common
|
||||
|
||||
import com.google.googlejavaformat.java.FormatterException
|
||||
import com.google.googlejavaformat.java.ImportOrderer
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireEditor
|
||||
import com.itsaky.androidide.editor.api.IEditor
|
||||
import com.itsaky.androidide.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.JavaServerSettings
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class OrganizeImportsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "lsp_java_organizeImports"
|
||||
override var label: String = ""
|
||||
override val titleTextRes: Int = string.action_organize_imports
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(OrganizeImportsAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.hasRequiredData(CodeEditor::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val watch = com.itsaky.androidide.utils.StopWatch("Organize imports")
|
||||
return try {
|
||||
val editor = data.requireEditor()
|
||||
val content = editor.text
|
||||
val server = data[JavaLanguageServer::class.java]
|
||||
val settings = server!!.settings as JavaServerSettings
|
||||
val output = ImportOrderer.reorderImports(content.toString(), settings.style)
|
||||
watch.log()
|
||||
output
|
||||
} catch (e: FormatterException) {
|
||||
log.error("Failed to reorder imports", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
super.postExec(data, result)
|
||||
if (result is String) {
|
||||
if (result.isNotEmpty()) {
|
||||
val editor = data.requireEditor()
|
||||
val cursor = editor.cursor.left()
|
||||
|
||||
editor.text.apply {
|
||||
val endLine = getLine(lineCount - 1)
|
||||
replace(0, 0, lineCount - 1, endLine.length + endLine.lineSeparator.length, result)
|
||||
}
|
||||
|
||||
(editor as? IEditor?)?.also {
|
||||
it.setSelectionAround(cursor)
|
||||
editor.ensureSelectionVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.itsaky.androidide.lsp.java.actions.common
|
||||
|
||||
import com.google.googlejavaformat.java.FormatterException
|
||||
import com.google.googlejavaformat.java.RemoveUnusedImports
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireEditor
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class RemoveUnusedImportsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.removeUnusedImports"
|
||||
override var label: String = ""
|
||||
override val titleTextRes: Int = string.action_remove_unused_imports
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(RemoveUnusedImportsAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.hasRequiredData(CodeEditor::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val watch = com.itsaky.androidide.utils.StopWatch("Remove unused imports")
|
||||
return try {
|
||||
val editor = data.requireEditor()
|
||||
val content = editor.text
|
||||
val output = RemoveUnusedImports.removeUnusedImports(content.toString())
|
||||
watch.log()
|
||||
output
|
||||
} catch (e: FormatterException) {
|
||||
log.error("Failed to remove unused imports", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result is String && result.isNotEmpty()) {
|
||||
val editor = data.requireEditor()
|
||||
editor.setText(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.common
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireEditor
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.resources.R
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class UncommentAction : BaseJavaCodeAction() {
|
||||
override val id: String = "ide.editor.lsp.java.uncommentLine"
|
||||
override var label: String = ""
|
||||
|
||||
override val titleTextRes: Int = R.string.action_uncomment_line
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override suspend fun execAction(data: ActionData): Boolean {
|
||||
val editor = data.requireEditor()
|
||||
val text = editor.text
|
||||
val cursor = editor.cursor
|
||||
|
||||
text.beginBatchEdit()
|
||||
for (line in cursor.leftLine..cursor.rightLine) {
|
||||
val l = text.getLineString(line)
|
||||
if (l.trim().startsWith("//")) {
|
||||
val i = l.indexOf("//")
|
||||
text.delete(line, i, line, i + 2)
|
||||
}
|
||||
}
|
||||
text.endBatchEdit()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun dismissOnAction() = false
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.google.common.collect.Iterables.toArray
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.newDialogBuilder
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddImport
|
||||
import com.itsaky.androidide.lsp.java.rewrite.Rewrite
|
||||
import com.itsaky.androidide.lsp.models.CodeActionItem
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import jdkx.tools.Diagnostic
|
||||
import jdkx.tools.JavaFileObject
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AddImportAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.addImport"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.NOT_IMPORTED.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_import_classes
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(AddImportAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code || diagnostic.extra !is Diagnostic<*>) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val file = data.requireFile()
|
||||
val module =
|
||||
IProjectManager.getInstance().findModuleForFile(file, false)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val jcDiagnostic =
|
||||
JavaDiagnosticUtils.asJCDiagnostic(diagnostic.extra as Diagnostic<out JavaFileObject>)
|
||||
if (jcDiagnostic == null) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val found =
|
||||
jcDiagnostic.args[1]?.toString()?.let { compiler.findQualifiedNames(it, true).isNotEmpty() }
|
||||
?: false
|
||||
|
||||
visible = found
|
||||
enabled = found
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val diagnostic =
|
||||
JavaDiagnosticUtils.asUnwrapper(
|
||||
data.get(DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
|
||||
)!!
|
||||
val file = data.requireFile()
|
||||
val module =
|
||||
IProjectManager.getInstance().findModuleForFile(file, false)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return Any()
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
|
||||
val titles = mutableListOf<String>()
|
||||
val rewrites = mutableListOf<AddImport>()
|
||||
val simpleName = diagnostic.d.args[1]
|
||||
for (name in compiler.publicTopLevelTypes()) {
|
||||
var klass = name
|
||||
if (klass.contains('/')) {
|
||||
klass = klass.replace('/', '.')
|
||||
}
|
||||
|
||||
if (!klass.endsWith(".$simpleName")) {
|
||||
continue
|
||||
}
|
||||
|
||||
titles.add(klass)
|
||||
rewrites.add(AddImport(data.requirePath(), klass))
|
||||
}
|
||||
|
||||
if (rewrites.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Pair(titles, rewrites)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
|
||||
if (result !is Pair<*, *>) {
|
||||
return
|
||||
}
|
||||
|
||||
val file = data.requireFile()
|
||||
val module =
|
||||
IProjectManager.getInstance().findModuleForFile(file, false)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val compiler = JavaCompilerProvider.get(module)
|
||||
val client = data.getLanguageClient() ?: return
|
||||
val actions = mutableListOf<CodeActionItem>()
|
||||
val titles = result.first as List<String>
|
||||
val rewrites = result.second as List<Rewrite>
|
||||
|
||||
for (index in rewrites.indices) {
|
||||
val name = titles[index]
|
||||
val rewrite = rewrites[index]
|
||||
rewrite.asCodeActions(compiler, name)?.let { actions.add(it) }
|
||||
}
|
||||
|
||||
when (actions.size) {
|
||||
0 -> {
|
||||
log.warn("No rewrites found. Cannot perform action")
|
||||
}
|
||||
|
||||
1 -> {
|
||||
client.performCodeAction(actions[0])
|
||||
}
|
||||
|
||||
else -> {
|
||||
val builder = newDialogBuilder(data)
|
||||
builder.setTitle(label)
|
||||
builder.setItems(toArray(titles, String::class.java)) { d, w ->
|
||||
d.dismiss()
|
||||
client.performCodeAction(actions[w])
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddException
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AddThrowsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id = "ide.editor.lsp.java.diagnostics.addThrows"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.NOT_THROWN.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_add_throws
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(AddThrowsAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
return compiler.compile(file).get { task ->
|
||||
val needsThrow = CodeActionUtils.findMethod(task, diagnostic.range)
|
||||
val exceptionName = CodeActionUtils.extractExceptionName(diagnostic.message)
|
||||
return@get AddException(
|
||||
needsThrow.className,
|
||||
needsThrow.methodName,
|
||||
needsThrow.erasedParameterTypes,
|
||||
exceptionName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is AddException) {
|
||||
log.warn("Unable to add 'throws' expression")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireContext
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.R
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.utils.positionForImports
|
||||
import com.itsaky.androidide.lsp.models.CodeActionItem
|
||||
import com.itsaky.androidide.lsp.models.CodeActionKind
|
||||
import com.itsaky.androidide.lsp.models.DocumentChange
|
||||
import com.itsaky.androidide.lsp.models.TextEdit
|
||||
import com.itsaky.androidide.models.Range
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.flashInfo
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Analyzes the source file for unresolved names and tries to import all of them at once.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class AutoFixImportsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val titleTextRes: Int = R.string.title_fix_imports
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.autoFixImports"
|
||||
override var label: String = ""
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(AutoFixImportsAction::class.java)
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Result {
|
||||
val path = data.requirePath()
|
||||
val compiler = data.requireCompiler()
|
||||
return compiler.compile(path).get { task ->
|
||||
val classes = mutableMapOf<String, List<String>>()
|
||||
|
||||
// find all unresolved simple names
|
||||
unresolvedNames(path, task).forEach { simpleName ->
|
||||
|
||||
// if we have already looked for this simple name
|
||||
// we do not need to look it up again
|
||||
if (classes[simpleName] != null) return@forEach
|
||||
|
||||
// find classes with those names
|
||||
compiler.findQualifiedNames(simpleName).let { names ->
|
||||
|
||||
// if we find classes with that specific simple name, map them to the simple name
|
||||
if (names.isNotEmpty()) {
|
||||
classes[simpleName] = names
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the result
|
||||
Result(getFileImports(task, path), classes)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is Result) {
|
||||
log.error("Invalid result returned from execAction: {}", result)
|
||||
return
|
||||
}
|
||||
|
||||
if (result.classes.isEmpty()) {
|
||||
flashInfo(R.string.msg_no_unresolved_classes)
|
||||
return
|
||||
}
|
||||
|
||||
// if there are multiple classes with same simple name
|
||||
// ask the user to choose the appropriate class
|
||||
if (result.classes.any { it.value.size > 1 }) {
|
||||
finalizeClassNames(data, result)
|
||||
} else {
|
||||
performEdits(data, result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun finalizeClassNames(data: ActionData, result: Result) {
|
||||
var e: Map.Entry<String, List<String>>? = null
|
||||
for (entry in result.classes) {
|
||||
if (entry.value.size > 1) {
|
||||
e = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (e == null) {
|
||||
performEdits(data, result)
|
||||
return
|
||||
}
|
||||
|
||||
val context = data.requireContext()
|
||||
DialogUtils.newMaterialDialogBuilder(context)
|
||||
.setCancelable(true)
|
||||
.setItems(e.value.toTypedArray()) { dialog, which ->
|
||||
dialog.dismiss()
|
||||
result.classes[e.key] = listOf(e.value[which])
|
||||
|
||||
// once the user decides which class to import for this simple name,
|
||||
// call this method again to see if there any other simple names with multiple options
|
||||
finalizeClassNames(data, result)
|
||||
}
|
||||
.setTitle(context.getString(R.string.title_class_chooser, e.key))
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun performEdits(data: ActionData, result: Result) {
|
||||
val path = data.requirePath()
|
||||
val compiler = data.requireCompiler()
|
||||
val client =
|
||||
data.getLanguageClient()
|
||||
?: run {
|
||||
log.warn("No language client found. Cannot perform edits.")
|
||||
return
|
||||
}
|
||||
|
||||
val classes = result.classes.mapNotNull { it.value.firstOrNull() }
|
||||
|
||||
if (classes.isEmpty()) {
|
||||
flashInfo(R.string.msg_no_unresolved_classes)
|
||||
return
|
||||
}
|
||||
|
||||
val insertText = StringBuilder()
|
||||
if (result.fileImports.isEmpty() && classes.isNotEmpty()) {
|
||||
// if there are no file imports, the new imports will be added just after the package
|
||||
// declaration. To avoid this, add a new line before the imports
|
||||
insertText.append("\n")
|
||||
}
|
||||
|
||||
for (klass in classes) {
|
||||
insertText.append("import ${klass};\n")
|
||||
}
|
||||
|
||||
val position = compiler.compile(path).get { positionForImports(classes[0], it) }
|
||||
|
||||
val change = DocumentChange()
|
||||
change.file = path
|
||||
change.edits = listOf(TextEdit(Range.pointRange(position), insertText.toString()))
|
||||
|
||||
val action = CodeActionItem()
|
||||
action.title = data.requireContext().getString(R.string.title_fix_imports)
|
||||
action.kind = CodeActionKind.QuickFix
|
||||
action.changes = listOf(change)
|
||||
client.performCodeAction(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through the diagnostics of the compilation task, looks for [DiagnosticCode.NOT_IMPORTED]
|
||||
* errors and returns a list of simple names of all not imported classes.
|
||||
*/
|
||||
private fun unresolvedNames(file: Path, task: CompileTask): List<String> {
|
||||
val names = mutableListOf<String>()
|
||||
var docContents: CharSequence? = null
|
||||
val diagnostics =
|
||||
task.diagnostics.filter {
|
||||
it.source.toUri() == file.toUri() && it.code == DiagnosticCode.NOT_IMPORTED.id
|
||||
}
|
||||
for (diagnostic in diagnostics) {
|
||||
val content =
|
||||
try {
|
||||
docContents ?: diagnostic.source.getCharContent(true).also { docContents = it }
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to get contents of file {}", file, e)
|
||||
continue
|
||||
}
|
||||
|
||||
val name =
|
||||
content.subSequence(diagnostic.startPosition.toInt(), diagnostic.endPosition.toInt())
|
||||
names.add(name.toString())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
private fun getFileImports(task: CompileTask, file: Path): Set<String> {
|
||||
return task.root(file).imports.map { it.qualifiedIdentifier }.map { it.toString() }.toSet()
|
||||
}
|
||||
|
||||
inner class Result(val fileImports: Set<String>, val classes: MutableMap<String, List<String>>)
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.CreateMissingMethod
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CreateMissingMethodAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.createMissingMethod"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.MISSING_METHOD.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_create_missing_method
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(CreateMissingMethodAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (
|
||||
!visible ||
|
||||
!data.hasRequiredData(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
return compiler.compile(file).get {
|
||||
CreateMissingMethod(file, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is CreateMissingMethod) {
|
||||
log.warn("Unable to create missing method")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.ConvertFieldToBlock
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.lsp.models.DiagnosticItem
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FieldToBlockAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.fieldToBlock"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.UNUSED_FIELD.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_convert_to_block
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(FieldToBlockAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.hasRequiredData(DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val diagnostic = data[DiagnosticItem::class.java]!!
|
||||
val file = data.requirePath()
|
||||
|
||||
return compiler.compile(file).get {
|
||||
ConvertFieldToBlock(file, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is ConvertFieldToBlock) {
|
||||
log.warn("Unable to convert field to block")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.javac.services.util.JavaDiagnosticUtils
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.ImplementAbstractMethods
|
||||
import com.itsaky.androidide.resources.R
|
||||
import jdkx.tools.Diagnostic
|
||||
import jdkx.tools.JavaFileObject
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class ImplementAbstractMethodsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.implementAbstractMethods"
|
||||
override var label: String = ""
|
||||
private var diagnosticCode = DiagnosticCode.DOES_NOT_OVERRIDE_ABSTRACT.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_implement_abstract_methods
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(ImplementAbstractMethodsAction::class.java)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.hasRequiredData(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code || diagnostic.extra !is Diagnostic<*>) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
JavaDiagnosticUtils.asJCDiagnostic(diagnostic.extra as Diagnostic<out JavaFileObject>)
|
||||
?: run {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic =
|
||||
JavaDiagnosticUtils.asJCDiagnostic(
|
||||
data.get(
|
||||
com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
|
||||
)
|
||||
return ImplementAbstractMethods(diagnostic!!)
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is ImplementAbstractMethods) {
|
||||
log.warn("Unable to perform action. Invalid result from execAction(..)")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.RemoveClass
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RemoveClassAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.removeClass"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.UNUSED_CLASS.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_remove_class
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(RemoveClassAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(
|
||||
com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
|
||||
return compiler.compile(file).get {
|
||||
RemoveClass(file, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is RemoveClass) {
|
||||
log.warn("Unable to remove class")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.RemoveMethod
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findMethod
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RemoveMethodAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.removeMethod"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.UNUSED_METHOD.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_remove_method
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(RemoveMethodAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(
|
||||
com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
|
||||
return compiler.compile(file).get {
|
||||
val unusedMethod = findMethod(it, diagnostic.range)
|
||||
RemoveMethod(
|
||||
unusedMethod.className,
|
||||
unusedMethod.methodName,
|
||||
unusedMethod.erasedParameterTypes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is RemoveMethod) {
|
||||
log.warn("Unable to remove method")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.RemoveException
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RemoveUnusedThrowsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.removeUnusedThrows"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.UNUSED_THROWS.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_remove_unused_throws
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(RemoveUnusedThrowsAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (
|
||||
!visible ||
|
||||
!data.hasRequiredData(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val d = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
return compiler.compile(file).get { task ->
|
||||
val notThrown = CodeActionUtils.extractNotThrownExceptionName(d.message)
|
||||
val methodWithExtraThrow = CodeActionUtils.findMethod(task, d.range)
|
||||
|
||||
return@get RemoveException(
|
||||
methodWithExtraThrow.className,
|
||||
methodWithExtraThrow.methodName,
|
||||
methodWithExtraThrow.erasedParameterTypes,
|
||||
notThrown
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is RemoveException) {
|
||||
log.warn("Unable to remove unused throws")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddSuppressWarningAnnotation
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SuppressUncheckedWarningAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id = "ide.editor.lsp.java.diagnostics.suppressUncheckedWarning"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.UNCHECKED.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_suppress_unchecked_warning
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(SuppressUncheckedWarningAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible || !data.hasRequiredData(
|
||||
com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
return compiler.compile(file).get { task ->
|
||||
val warnedMethod = CodeActionUtils.findMethod(task, diagnostic.range)
|
||||
return@get AddSuppressWarningAnnotation(
|
||||
warnedMethod.className,
|
||||
warnedMethod.methodName,
|
||||
warnedMethod.erasedParameterTypes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is AddSuppressWarningAnnotation) {
|
||||
log.warn("Unable to suppress 'unchecked' warning")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.diagnostics
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.ConvertVariableToStatement
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils.findPosition
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class VariableToStatementAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.diagnostics.variableToStatement"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.UNUSED_LOCAL.id
|
||||
|
||||
override val titleTextRes: Int = R.string.action_convert_to_statement
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(VariableToStatementAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.hasRequiredData(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val path = data.requirePath()
|
||||
|
||||
return compiler.compile(path).get {
|
||||
ConvertVariableToStatement(path, findPosition(it, diagnostic.range.start))
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is ConvertVariableToStatement) {
|
||||
log.warn("Unable to convert variable to statement")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.actions.generators
|
||||
|
||||
import android.content.Context
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.Modifier
|
||||
import com.github.javaparser.ast.body.ConstructorDeclaration
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.ShortTypePrinter.NO_PACKAGE
|
||||
import com.itsaky.androidide.preferences.utils.indentationString
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import openjdk.source.tree.ClassTree
|
||||
import openjdk.source.tree.VariableTree
|
||||
import openjdk.source.util.TreePath
|
||||
import openjdk.tools.javac.api.JavacTrees
|
||||
import openjdk.tools.javac.code.Symbol.ClassSymbol
|
||||
import openjdk.tools.javac.code.Symbol.VarSymbol
|
||||
import openjdk.tools.javac.code.Type
|
||||
import openjdk.tools.javac.tree.JCTree
|
||||
import openjdk.tools.javac.tree.TreeInfo
|
||||
import openjdk.tools.javac.util.ListBuffer
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* Allows the user to select fields and generate a constructor which has parameters same as the
|
||||
* selected fields.
|
||||
*
|
||||
* The generated constructor has statements which assigns the fields to the parameter types.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GenerateConstructorAction : FieldBasedAction() {
|
||||
|
||||
override val titleTextRes: Int = string.action_generate_constructor
|
||||
override val id: String = "ide.editor.lsp.java.generator.constructor"
|
||||
override var label: String = ""
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(GenerateConstructorAction::class.java)
|
||||
}
|
||||
|
||||
override fun onGetFields(fields: List<String>, data: ActionData) {
|
||||
showFieldSelector(fields, data) { selected ->
|
||||
CompletableFuture.runAsync { generateConstructor(data, selected) }
|
||||
.whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to generate constructor for the selected fields", error)
|
||||
flashError(string.msg_cannot_generate_constructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConstructor(data: ActionData, selected: MutableSet<String>) {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = data.requirePath()
|
||||
|
||||
compiler.compile(file).run { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val typeFinder = triple.first
|
||||
val type = triple.second
|
||||
val fields = triple.third
|
||||
|
||||
fields.removeIf { !selected.contains("${it.name}: ${it.type}") }
|
||||
|
||||
log.debug("Creating toString() method with fields: {}", fields.map { it.name })
|
||||
|
||||
generateForFields(data, task, type, fields.map { TreePath(typeFinder.path, it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateForFields(
|
||||
data: ActionData,
|
||||
task: CompileTask,
|
||||
type: ClassTree,
|
||||
paths: List<TreePath>
|
||||
) {
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
val trees = JavacTrees.instance(task.task)
|
||||
val sym = TreeInfo.symbolFor(type as JCTree) as ClassSymbol
|
||||
val varTypes = mapTypes(paths)
|
||||
val varNames = paths.map { it.leaf as VariableTree }.map { it.name.toString() }
|
||||
|
||||
if (paths.isEmpty() || trees.findConstructor(sym, varTypes) != null) {
|
||||
log.warn(
|
||||
"A constructor with same parameter types is already available in class {}", type.simpleName
|
||||
)
|
||||
flashError(data[Context::class.java]!!.getString(string.msg_constructor_available))
|
||||
return
|
||||
}
|
||||
|
||||
val stopWatch = com.itsaky.androidide.utils.StopWatch("generateConstructorForFields()")
|
||||
val constructor =
|
||||
newConstructor(type.simpleName.toString(), varTypes.toTypedArray(), varNames.toTypedArray())
|
||||
val body = constructor.createBody()
|
||||
for (varName in varNames) {
|
||||
body.addStatement(StaticJavaParser.parseStatement("this.$varName = $varName;"))
|
||||
}
|
||||
|
||||
stopWatch.lap("Constructor generated")
|
||||
log.info("Inserting constructor into editor...")
|
||||
|
||||
val insertAt = EditHelper.insertAfter(task.task, task.root(), paths.last().leaf)
|
||||
val indent = EditHelper.indent(task.task, task.root(), paths.last().leaf)
|
||||
var text = constructor.toString()
|
||||
text = text.replace("\n", "\n${indentationString(indent)}")
|
||||
text += "\n"
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
editor.text.insert(insertAt.line, insertAt.column, text)
|
||||
editor.formatCodeAsync()
|
||||
stopWatch.log()
|
||||
}
|
||||
}
|
||||
|
||||
private fun newConstructor(
|
||||
name: String,
|
||||
paramTypes: Array<Type>,
|
||||
paramNames: Array<String>
|
||||
): ConstructorDeclaration {
|
||||
val constructor = ConstructorDeclaration()
|
||||
constructor.setName(name)
|
||||
constructor.addModifier(Modifier.Keyword.PUBLIC)
|
||||
|
||||
for (i in paramTypes.indices) {
|
||||
val paramType = paramTypes[i]
|
||||
val paramName = paramNames[i]
|
||||
|
||||
constructor.addParameter(NO_PACKAGE.print(paramType), paramName)
|
||||
}
|
||||
|
||||
return constructor
|
||||
}
|
||||
|
||||
private fun mapTypes(paths: List<TreePath>): openjdk.tools.javac.util.List<Type> {
|
||||
val buffer = ListBuffer<Type>()
|
||||
for (path in paths) {
|
||||
val leaf = path.leaf
|
||||
val sym = TreeInfo.symbolFor(leaf as JCTree) as VarSymbol
|
||||
buffer.add(sym.type)
|
||||
}
|
||||
|
||||
return buffer.toList()
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.generators
|
||||
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.models.DiagnosticCode
|
||||
import com.itsaky.androidide.lsp.java.rewrite.GenerateRecordConstructor
|
||||
import com.itsaky.androidide.lsp.java.utils.CodeActionUtils
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class GenerateMissingConstructorAction : BaseJavaCodeAction() {
|
||||
|
||||
override val id = "ide.editor.lsp.java.generator.missingConstructor"
|
||||
override var label: String = ""
|
||||
private val diagnosticCode = DiagnosticCode.MISSING_CONSTRUCTOR.id
|
||||
override val titleTextRes: Int = R.string.action_generate_missing_constructor
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(GenerateMissingConstructorAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (
|
||||
!visible ||
|
||||
!data.hasRequiredData(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
if (diagnosticCode != diagnostic.code) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val diagnostic = data[com.itsaky.androidide.lsp.models.DiagnosticItem::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
return compiler.compile(file).get { task ->
|
||||
val needsConstructor =
|
||||
CodeActionUtils.findClassNeedingConstructor(task, diagnostic.range) ?: return@get false
|
||||
return@get GenerateRecordConstructor(needsConstructor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is GenerateRecordConstructor) {
|
||||
log.warn("Unable to generate constructor")
|
||||
return
|
||||
}
|
||||
|
||||
performCodeAction(data, result)
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.generators
|
||||
|
||||
import android.content.Context
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.Modifier
|
||||
import com.github.javaparser.ast.body.MethodDeclaration
|
||||
import com.github.javaparser.ast.expr.SimpleName
|
||||
import com.github.javaparser.ast.stmt.BlockStmt
|
||||
import com.github.javaparser.ast.type.Type
|
||||
import com.github.javaparser.ast.type.VoidType
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.JavaParserUtils
|
||||
import com.itsaky.androidide.lsp.java.utils.TypeUtils.toType
|
||||
import com.itsaky.androidide.preferences.internal.EditorPreferences
|
||||
import com.itsaky.androidide.preferences.utils.indentationString
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import jdkx.lang.model.element.Modifier.FINAL
|
||||
import jdkx.lang.model.element.VariableElement
|
||||
import openjdk.source.tree.ClassTree
|
||||
import openjdk.source.util.TreePath
|
||||
import openjdk.source.util.Trees
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* Allows the user to select fields from the current class, then generates setters and getters for
|
||||
* the selected fields.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GenerateSettersAndGettersAction : FieldBasedAction() {
|
||||
|
||||
override val id: String = "ide.editor.lsp.java.generator.settersAndGetters"
|
||||
override var label: String = ""
|
||||
|
||||
override val titleTextRes: Int = R.string.action_generate_setters_getters
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(GenerateSettersAndGettersAction::class.java)
|
||||
}
|
||||
|
||||
override fun onGetFields(fields: List<String>, data: ActionData) {
|
||||
|
||||
showFieldSelector(fields, data) { checkedNames ->
|
||||
CompletableFuture.runAsync { generateForFields(data, checkedNames) }
|
||||
.whenComplete {
|
||||
_, error,
|
||||
->
|
||||
if (error != null) {
|
||||
log.error("Unable to generate setters and getters", error)
|
||||
ThreadUtils.runOnUiThread {
|
||||
flashError(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_generate_setters_getters)
|
||||
)
|
||||
}
|
||||
return@whenComplete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateForFields(data: ActionData, names: MutableSet<String>) {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = data.requirePath()
|
||||
|
||||
compiler.compile(file).run { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val typeFinder = triple.first
|
||||
val type = triple.second
|
||||
val fields = triple.third
|
||||
|
||||
fields.removeIf { !names.contains("${it.name}: ${it.type}") }
|
||||
|
||||
log.debug("Creating setters/getters for fields: {}", fields.map { it.name })
|
||||
|
||||
generateForFields(data, task, type, fields.map { TreePath(typeFinder.path, it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateForFields(
|
||||
data: ActionData,
|
||||
task: CompileTask,
|
||||
type: ClassTree,
|
||||
paths: List<TreePath>,
|
||||
) {
|
||||
val file = data.requirePath()
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
val trees = Trees.instance(task.task)
|
||||
val insert = EditHelper.insertAtEndOfClass(task.task, task.root(file), type)
|
||||
val sb = StringBuilder()
|
||||
|
||||
for (path in paths) {
|
||||
val element = trees.getElement(path) ?: continue
|
||||
if (element !is VariableElement) {
|
||||
continue
|
||||
}
|
||||
|
||||
val leaf = path.leaf
|
||||
val indent = EditHelper.indent(task.task, task.root(file), leaf) + EditorPreferences.tabSize
|
||||
sb.append(createGetter(element, indent))
|
||||
if (!element.modifiers.contains(FINAL)) {
|
||||
sb.append(createSetter(element, indent))
|
||||
}
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
editor.text.insert(insert.line, insert.column, sb)
|
||||
editor.formatCodeAsync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createGetter(variable: VariableElement, indent: Int): String {
|
||||
val name = variable.simpleName.toString()
|
||||
val method =
|
||||
createMethod(variable, "get", toType(variable.asType())) { _, body ->
|
||||
body.addStatement(createReturnStmt(name))
|
||||
}
|
||||
var text = "\n" + JavaParserUtils.prettyPrint(method) { false }
|
||||
text = text.replace("\n", "\n${indentationString(indent)}")
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
private fun createReturnStmt(name: String) = StaticJavaParser.parseStatement("return this.$name;")
|
||||
|
||||
private fun createSetter(variable: VariableElement, indent: Int): String {
|
||||
val name: String = variable.simpleName.toString()
|
||||
val method =
|
||||
createMethod(variable, "set", VoidType()) { method, body ->
|
||||
method.addParameter(toType(variable.asType()), name)
|
||||
body.addStatement(createAssignmentStmt(name))
|
||||
}
|
||||
|
||||
var text = "\n" + JavaParserUtils.prettyPrint(method) { false }
|
||||
text = text.replace("\n", "\n${indentationString(indent)}")
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
private fun createMethod(
|
||||
variable: VariableElement,
|
||||
prefix: String,
|
||||
returnType: Type,
|
||||
vararg modifiers: Modifier.Keyword = arrayOf(Modifier.Keyword.PUBLIC),
|
||||
block: (MethodDeclaration, BlockStmt) -> Unit
|
||||
): MethodDeclaration {
|
||||
val name = variable.simpleName.toString()
|
||||
val method = MethodDeclaration()
|
||||
val body = method.createBody()
|
||||
method.name = SimpleName(createName(name, prefix))
|
||||
method.type = returnType
|
||||
method.addModifier(*modifiers)
|
||||
block(method, body)
|
||||
return method
|
||||
}
|
||||
|
||||
private fun createAssignmentStmt(name: String) =
|
||||
StaticJavaParser.parseStatement("this.$name = $name;")
|
||||
|
||||
private fun createName(name: String, prefix: String): String {
|
||||
val sb = StringBuilder(name)
|
||||
sb.setCharAt(0, Character.toUpperCase(sb[0]))
|
||||
sb.insert(0, prefix)
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.generators
|
||||
|
||||
import android.content.Context
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.github.javaparser.StaticJavaParser
|
||||
import com.github.javaparser.ast.Modifier
|
||||
import com.github.javaparser.ast.body.MethodDeclaration
|
||||
import com.github.javaparser.ast.stmt.ReturnStmt
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.FieldBasedAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.preferences.internal.EditorPreferences
|
||||
import com.itsaky.androidide.preferences.utils.indentationString
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.resources.R.string
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import jdkx.lang.model.element.VariableElement
|
||||
import openjdk.source.tree.ClassTree
|
||||
import openjdk.source.tree.VariableTree
|
||||
import openjdk.source.util.TreePath
|
||||
import openjdk.tools.javac.api.JavacTrees
|
||||
import openjdk.tools.javac.code.Symbol.ClassSymbol
|
||||
import openjdk.tools.javac.code.Symbol.MethodSymbol
|
||||
import openjdk.tools.javac.tree.JCTree
|
||||
import openjdk.tools.javac.tree.TreeInfo
|
||||
import openjdk.tools.javac.util.Names
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* Generates the `toString()` method for the current class.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GenerateToStringMethodAction : FieldBasedAction() {
|
||||
|
||||
override val titleTextRes: Int = R.string.action_generate_toString
|
||||
override val id: String = "ide.editor.lsp.java.generator.toString"
|
||||
override var label: String = ""
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(GenerateToStringMethodAction::class.java)
|
||||
}
|
||||
|
||||
override fun onGetFields(fields: List<String>, data: ActionData) {
|
||||
showFieldSelector(fields, data) { selected ->
|
||||
CompletableFuture.runAsync { generateToString(data, selected) }
|
||||
.whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to generate toString() implementation", error)
|
||||
ThreadUtils.runOnUiThread {
|
||||
flashError(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_generate_toString)
|
||||
)
|
||||
}
|
||||
return@whenComplete
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateToString(data: ActionData, selected: MutableSet<String>) {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val file = data.requirePath()
|
||||
|
||||
compiler.compile(file).run { task ->
|
||||
val triple = findFields(task, file, range)
|
||||
val typeFinder = triple.first
|
||||
val type = triple.second
|
||||
val fields = triple.third
|
||||
|
||||
fields.removeIf { !selected.contains("${it.name}: ${it.type}") }
|
||||
|
||||
log.debug("Creating toString() method with fields: {}", fields.map { it.name })
|
||||
|
||||
generateForFields(data, task, type, fields.map { TreePath(typeFinder.path, it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateForFields(
|
||||
data: ActionData,
|
||||
task: CompileTask,
|
||||
type: ClassTree,
|
||||
paths: List<TreePath>
|
||||
) {
|
||||
if (isToStringOverridden(task, type)) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
flashError(data[Context::class.java]!!.getString(string.msg_toString_overridden))
|
||||
}
|
||||
log.warn("toString() method has already been overridden in class {}", type.simpleName)
|
||||
return
|
||||
}
|
||||
|
||||
val file = data.requirePath()
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
val trees = JavacTrees.instance(task.task)
|
||||
val indent = EditHelper.indent(task.task, task.root(), type) + EditorPreferences.tabSize
|
||||
val insert = EditHelper.insertAtEndOfClass(task.task, task.root(file), type)
|
||||
val string = StringBuilder()
|
||||
var isFirst = true
|
||||
|
||||
string.append("\"")
|
||||
string.append(type.simpleName)
|
||||
string.append('[')
|
||||
for (path in paths) {
|
||||
val element = trees.getElement(path) ?: continue
|
||||
if (element !is VariableElement) {
|
||||
continue
|
||||
}
|
||||
|
||||
val leaf = path.leaf as VariableTree
|
||||
if (!isFirst) {
|
||||
string.append(", ")
|
||||
}
|
||||
string.append(leaf.name)
|
||||
string.append("=")
|
||||
string.append("\" + ")
|
||||
string.append(leaf.name)
|
||||
string.append(" + \"")
|
||||
|
||||
// "ClassName[field1=' + field1 + ', field2=' + field2 + ']"
|
||||
|
||||
isFirst = false
|
||||
}
|
||||
string.append("]\"")
|
||||
|
||||
val method = overrideToString()
|
||||
val body = method.createBody()
|
||||
body.addStatement(createReturnStatement(string.toString()))
|
||||
|
||||
var text = "\n" + method.toString()
|
||||
text = text.replace("\n", "\n${indentationString(indent)}")
|
||||
text += "\n"
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
editor.text.insert(insert.line, insert.column, text)
|
||||
editor.formatCodeAsync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isToStringOverridden(task: CompileTask, type: ClassTree): Boolean {
|
||||
val names = Names.instance(task.task.context)
|
||||
val sym = TreeInfo.symbolFor(type as JCTree) as ClassSymbol
|
||||
val toStrings =
|
||||
sym.members().getSymbolsByName(names.toString).filterIsInstance<MethodSymbol>().filter {
|
||||
it.params.isEmpty()
|
||||
}
|
||||
|
||||
return toStrings.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun createReturnStatement(string: String): ReturnStmt {
|
||||
return StaticJavaParser.parseStatement("return $string;") as ReturnStmt
|
||||
}
|
||||
|
||||
private fun overrideToString(): MethodDeclaration {
|
||||
val method = MethodDeclaration()
|
||||
method.addMarkerAnnotation("Override")
|
||||
method.addModifier(Modifier.Keyword.PUBLIC)
|
||||
method.setType("String")
|
||||
method.setName("toString")
|
||||
return method
|
||||
}
|
||||
}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.actions.generators
|
||||
|
||||
import android.content.Context
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.hasRequiredData
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.newDialogBuilder
|
||||
import com.itsaky.androidide.actions.requireFile
|
||||
import com.itsaky.androidide.actions.requirePath
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.actions.BaseJavaCodeAction
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompileTask
|
||||
import com.itsaky.androidide.lsp.java.compiler.CompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask
|
||||
import com.itsaky.androidide.lsp.java.rewrite.AddImport
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.FindHelper
|
||||
import com.itsaky.androidide.lsp.java.utils.JavaParserUtils
|
||||
import com.itsaky.androidide.lsp.java.utils.MethodPtr
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarationAt
|
||||
import com.itsaky.androidide.models.Position
|
||||
import com.itsaky.androidide.preferences.internal.EditorPreferences
|
||||
import com.itsaky.androidide.preferences.utils.indentationString
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.resources.R
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import jdkx.lang.model.element.ElementKind
|
||||
import jdkx.lang.model.element.ExecutableElement
|
||||
import jdkx.lang.model.element.Modifier
|
||||
import jdkx.lang.model.element.TypeElement
|
||||
import jdkx.lang.model.type.DeclaredType
|
||||
import jdkx.lang.model.type.ExecutableType
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.source.tree.MethodTree
|
||||
import openjdk.source.util.Trees
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.Arrays
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* Allows the user to override multiple methods from superclass at once.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class OverrideSuperclassMethodsAction : BaseJavaCodeAction() {
|
||||
|
||||
override val titleTextRes: Int = R.string.action_override_superclass_methods
|
||||
override val id: String = "ide.editor.lsp.java.generator.overrideSuperclassMethods"
|
||||
override var label: String = ""
|
||||
private var position: Long = -1
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(OverrideSuperclassMethodsAction::class.java)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (
|
||||
!visible ||
|
||||
!data.hasRequiredData(
|
||||
com.itsaky.androidide.models.Range::class.java,
|
||||
CodeEditor::class.java
|
||||
)
|
||||
) {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override suspend fun execAction(data: ActionData): Any {
|
||||
val range = data[com.itsaky.androidide.models.Range::class.java]!!
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return Any())
|
||||
val file = data.requirePath()
|
||||
|
||||
return compiler.compile(file).get { task ->
|
||||
// 1-based line and column index
|
||||
val startLine = range.start.line + 1
|
||||
val startColumn = range.start.column + 1
|
||||
val endLine = range.end.line + 1
|
||||
val endColumn = range.end.column + 1
|
||||
val lines = task.root().lineMap
|
||||
val start = lines.getPosition(startLine.toLong(), startColumn.toLong())
|
||||
val end = lines.getPosition(endLine.toLong(), endColumn.toLong())
|
||||
|
||||
if (start == (-1).toLong() || end == (-1).toLong()) {
|
||||
return@get false
|
||||
}
|
||||
|
||||
this.position = start
|
||||
val typeFinder = FindTypeDeclarationAt(task.task)
|
||||
var type = typeFinder.scan(task.root(file), start)
|
||||
if (type == null) {
|
||||
type = typeFinder.scan(task.root(file), end)
|
||||
position = end
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
return@get false
|
||||
}
|
||||
|
||||
val overridable = mutableListOf<MethodPtr>()
|
||||
val trees = Trees.instance(task.task)
|
||||
val elements = task.task.elements
|
||||
val classPath = typeFinder.path
|
||||
val element = trees.getElement(classPath) as TypeElement
|
||||
|
||||
for (member in elements.getAllMembers(element)) {
|
||||
if (member.modifiers.contains(Modifier.FINAL) || member.kind != ElementKind.METHOD) {
|
||||
continue
|
||||
}
|
||||
|
||||
val method = member as ExecutableElement
|
||||
val methodSource = member.getEnclosingElement() as TypeElement
|
||||
if (
|
||||
methodSource.qualifiedName.contentEquals("java.lang.Object") || methodSource == element
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
val pointer = MethodPtr(task.task, method)
|
||||
overridable.add(pointer)
|
||||
}
|
||||
|
||||
return@get overridable
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result !is List<*> || result.isEmpty() || position < 0) {
|
||||
log.warn("Unable to find any overridable method")
|
||||
flashError(data[Context::class.java]!!.getString(R.string.msg_no_overridable_methods))
|
||||
return
|
||||
}
|
||||
|
||||
val methods = (result as List<MethodPtr>).sortedBy { it.methodName }
|
||||
val checkedMethods = mutableListOf<MethodPtr>()
|
||||
val names = mapMethodPointers(methods)
|
||||
val builder = newDialogBuilder(data)
|
||||
builder.setTitle(data[Context::class.java]!!.getString(R.string.msg_select_methods))
|
||||
builder.setMultiChoiceItems(names, BooleanArray(names.size)) { _, which, isChecked ->
|
||||
checkedMethods.apply {
|
||||
if (isChecked) {
|
||||
add(methods[which])
|
||||
} else {
|
||||
remove(methods[which])
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
|
||||
if (checkedMethods.isEmpty()) {
|
||||
flashError(data[Context::class.java]!!.getString(R.string.msg_no_methods_selected))
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
CompletableFuture.runAsync { overrideMethods(data, checkedMethods) }
|
||||
.whenComplete {
|
||||
_, error,
|
||||
->
|
||||
if (error != null) {
|
||||
log.error("An error occurred overriding methods")
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
flashError(
|
||||
data[Context::class.java]!!.getString(R.string.msg_cannot_override_methods)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun overrideMethods(data: ActionData, checkedMethods: MutableList<MethodPtr>) {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
|
||||
val file = data.requirePath()
|
||||
|
||||
compiler.compile(file).run { task ->
|
||||
val types = task.task.types
|
||||
val trees = Trees.instance(task.task)
|
||||
val sb = StringBuilder()
|
||||
val imports = mutableSetOf<String>()
|
||||
|
||||
val typeFinder = FindTypeDeclarationAt(task.task)
|
||||
val classTree = typeFinder.scan(task.root(), position)
|
||||
val thisClass = trees.getElement(typeFinder.path) as TypeElement
|
||||
val indent = EditHelper.indent(task.task, task.root(), classTree) + EditorPreferences.tabSize
|
||||
val fileImports = task.root(file).imports.map { it.qualifiedIdentifier.toString() }.toSet()
|
||||
val filePackage = task.root(file).`package`.packageName.toString()
|
||||
|
||||
for (pointer in checkedMethods) {
|
||||
val superMethod =
|
||||
FindHelper.findMethod(
|
||||
task,
|
||||
pointer.className,
|
||||
pointer.methodName,
|
||||
pointer.erasedParameterTypes
|
||||
) ?: continue
|
||||
|
||||
val thisDeclaredType = thisClass.asType() as DeclaredType
|
||||
val executableType = types.asMemberOf(thisDeclaredType, superMethod) as ExecutableType
|
||||
val source = findSource(compiler, task, superMethod)
|
||||
val method =
|
||||
if (source != null) {
|
||||
JavaParserUtils.printMethod(superMethod, executableType, source)
|
||||
} else {
|
||||
JavaParserUtils.printMethod(superMethod, executableType, superMethod)
|
||||
}
|
||||
|
||||
val newImports = JavaParserUtils.collectImports(executableType)
|
||||
sb.append("\n")
|
||||
sb.append(method.toString())
|
||||
sb.replace(Regex(Regex.escape("\n")), "\n${indentationString(indent)}")
|
||||
sb.append("\n")
|
||||
|
||||
newImports.removeIf {
|
||||
it.startsWith("java.lang.") || it.startsWith(filePackage) || fileImports.contains(it)
|
||||
}
|
||||
|
||||
imports.addAll(newImports)
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
performEdits(
|
||||
data,
|
||||
sb,
|
||||
imports,
|
||||
EditHelper.insertAtEndOfClass(task.task, task.root(file), classTree)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performEdits(
|
||||
data: ActionData,
|
||||
sb: StringBuilder,
|
||||
imports: MutableSet<String>,
|
||||
position: Position,
|
||||
) {
|
||||
val compiler =
|
||||
JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(data.requireFile(), false) ?: return)
|
||||
val editor = data[CodeEditor::class.java]!!
|
||||
val file = data.requirePath()
|
||||
val text = editor.text
|
||||
|
||||
text.beginBatchEdit()
|
||||
|
||||
text.insert(position.line, position.column, sb)
|
||||
|
||||
for (name in imports) {
|
||||
val rewrite = AddImport(file, name)
|
||||
val edits = rewrite.rewrite(compiler)[file]
|
||||
if (edits.isNullOrEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (edit in edits) {
|
||||
if (edit.range.start == edit.range.end) {
|
||||
text.insert(edit.range.start.line, edit.range.start.column, edit.newText)
|
||||
} else {
|
||||
text.replace(
|
||||
edit.range.start.line,
|
||||
edit.range.start.column,
|
||||
edit.range.end.line,
|
||||
edit.range.end.column,
|
||||
edit.newText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text.endBatchEdit()
|
||||
editor.formatCodeAsync()
|
||||
}
|
||||
|
||||
private fun mapMethodPointers(methods: List<MethodPtr>): Array<CharSequence> {
|
||||
return methods
|
||||
.map { Arrays.toString(it.simplifiedErasedParameterTypes) }
|
||||
.map {
|
||||
val arr = it.toCharArray()
|
||||
arr[0] = '('
|
||||
arr[arr.size - 1] = ')'
|
||||
|
||||
String(arr)
|
||||
}
|
||||
.mapIndexed { index, params -> "${methods[index].methodName}$params" }
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
private fun findSource(
|
||||
compiler: CompilerProvider,
|
||||
task: CompileTask,
|
||||
method: ExecutableElement,
|
||||
): MethodTree? {
|
||||
val superClass = method.enclosingElement as TypeElement
|
||||
val superClassName = superClass.qualifiedName.toString()
|
||||
val methodName = method.simpleName.toString()
|
||||
val erasedParameterTypes = FindHelper.erasedParameterTypes(task, method)
|
||||
val sourceFile: Optional<JavaFileObject> = compiler.findAnywhere(superClassName)
|
||||
if (!sourceFile.isPresent) return null
|
||||
val parse: ParseTask = compiler.parse(sourceFile.get())
|
||||
return FindHelper.findMethod(parse, superClassName, methodName, erasedParameterTypes)
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler
|
||||
|
||||
import openjdk.source.tree.CompilationUnitTree
|
||||
import openjdk.tools.javac.api.JavacTaskImpl
|
||||
import java.util.function.*
|
||||
|
||||
/**
|
||||
* A compilation task processor process the [JavacTaskImpl]. Usually, a processor decides what files
|
||||
* should be parsed and analyzed.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
fun interface CompilationTaskProcessor {
|
||||
|
||||
/**
|
||||
* Process the given [JavacTaskImpl]. The processor is responsible for parsing and analyzing the
|
||||
* task. For each parsed [CompilationUnitTree], [processCompilationUnit] must be called.
|
||||
*/
|
||||
@Throws(Throwable::class)
|
||||
fun process(task: JavacTaskImpl, processCompilationUnit: Consumer<CompilationUnitTree>)
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import static com.itsaky.androidide.javac.config.JavacConfigProvider.PROP_ANDROIDIDE_JAVA_HOME;
|
||||
import static com.itsaky.androidide.javac.config.JavacConfigProvider.disableModules;
|
||||
import static com.itsaky.androidide.javac.config.JavacConfigProvider.enableModules;
|
||||
import static com.itsaky.androidide.javac.config.JavacConfigProvider.setLatestSourceVersion;
|
||||
import static com.itsaky.androidide.javac.config.JavacConfigProvider.setLatestSupportedSourceVersion;
|
||||
import static com.itsaky.androidide.utils.Environment.JAVA_HOME;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import com.itsaky.androidide.builder.model.IJavaCompilerSettings;
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableBorrow;
|
||||
import com.itsaky.androidide.javac.services.partial.DiagnosticListenerImpl;
|
||||
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
|
||||
import com.itsaky.androidide.lsp.java.visitors.MethodRangeScanner;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.util.StringSearch;
|
||||
import com.itsaky.androidide.tooling.api.ProjectType;
|
||||
import com.itsaky.androidide.utils.ClassTrie;
|
||||
import com.itsaky.androidide.utils.SourceClassTrie;
|
||||
import com.itsaky.androidide.utils.StopWatch;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import jdkx.lang.model.SourceVersion;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.tools.javac.api.ClientCodeWrapper;
|
||||
import openjdk.tools.javac.api.JavacTaskImpl;
|
||||
import openjdk.tools.javac.code.Kinds;
|
||||
import openjdk.tools.javac.util.JCDiagnostic;
|
||||
|
||||
public class CompileBatch implements AutoCloseable {
|
||||
|
||||
public static final String DEFAULT_COMPILER_SOURCE_AND_TARGET_VERSION = "11";
|
||||
protected final JavaCompilerService parent;
|
||||
protected final ReusableBorrow borrow;
|
||||
protected final JavacTaskImpl task;
|
||||
protected final List<CompilationUnitTree> roots;
|
||||
protected final Map<String, List<Pair<Range, TreePath>>> methodPositions = new HashMap<>();
|
||||
protected DiagnosticListenerImpl diagnosticListener;
|
||||
/** Indicates the task that requested the compilation is finished with it. */
|
||||
boolean closed;
|
||||
|
||||
CompileBatch(
|
||||
JavaCompilerService parent,
|
||||
Collection<? extends JavaFileObject> files,
|
||||
CompilationRequest compilationRequest) {
|
||||
this.parent = parent;
|
||||
this.borrow = batchTask(parent, files);
|
||||
this.task = borrow.task;
|
||||
this.roots = new ArrayList<>();
|
||||
|
||||
final var context = task.getContext();
|
||||
final var config = JavaCompilerConfig.instance(context);
|
||||
config.setFiles(files);
|
||||
|
||||
if (compilationRequest.configureContext != null) {
|
||||
compilationRequest.configureContext.accept(context);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(compilationRequest, "A task processor is required");
|
||||
|
||||
try {
|
||||
compilationRequest.compilationTaskProcessor.process(borrow.task, this::processCompilationUnit);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
config.setFiles(null);
|
||||
}
|
||||
|
||||
private void processCompilationUnit(final CompilationUnitTree root) {
|
||||
roots.add(root);
|
||||
// updatePositions(root, false);
|
||||
}
|
||||
|
||||
void updatePositions(CompilationUnitTree tree, boolean allowDuplicate) {
|
||||
final StopWatch watch = new StopWatch("Scan method positions");
|
||||
final List<Pair<Range, TreePath>> positions = new ArrayList<>();
|
||||
new MethodRangeScanner(this.task).scan(tree, positions);
|
||||
final String path = new File(tree.getSourceFile().toUri()).getAbsolutePath();
|
||||
final List<Pair<Range, TreePath>> old = this.methodPositions.put(path, positions);
|
||||
if (old != null && !allowDuplicate) {
|
||||
throw new IllegalStateException(
|
||||
"Duplicate CompilationUnitTree for file:" + tree.getSourceFile().toUri());
|
||||
}
|
||||
|
||||
watch.log();
|
||||
}
|
||||
|
||||
private ReusableBorrow batchTask(
|
||||
@NonNull JavaCompilerService parent, @NonNull Collection<? extends JavaFileObject> sources) {
|
||||
|
||||
parent.diagnostics.clear();
|
||||
final Iterable<String> options = options();
|
||||
|
||||
diagnosticListener =
|
||||
new DiagnosticListenerWrapper(parent.diagnostics::add, sources.iterator().next());
|
||||
|
||||
final ReusableBorrow borrow =
|
||||
parent.compiler.getTask(
|
||||
parent.fileManager, diagnosticListener, options, Collections.emptyList(), sources);
|
||||
|
||||
if (parent.fileManager != null) {
|
||||
parent.fileManager.setContext(borrow.task.getContext());
|
||||
}
|
||||
|
||||
return borrow;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<String> options() {
|
||||
List<String> options = new ArrayList<>();
|
||||
|
||||
// This won't be used if the current module is Android module project
|
||||
System.setProperty(PROP_ANDROIDIDE_JAVA_HOME, JAVA_HOME.getAbsolutePath());
|
||||
if (this.parent.module != null && this.parent.module.getType() == ProjectType.Android) {
|
||||
setLatestSourceVersion(SourceVersion.RELEASE_8);
|
||||
setLatestSupportedSourceVersion(SourceVersion.RELEASE_11);
|
||||
disableModules();
|
||||
} else {
|
||||
setLatestSourceVersion(SourceVersion.RELEASE_11);
|
||||
setLatestSupportedSourceVersion(SourceVersion.RELEASE_11);
|
||||
enableModules();
|
||||
}
|
||||
|
||||
setupCompileOptions(parent.module, options);
|
||||
Collections.addAll(options, "-proc:none", "-g");
|
||||
|
||||
Collections.addAll(
|
||||
options,
|
||||
"-XDcompilePolicy=byfile",
|
||||
"-XD-Xprefer=source",
|
||||
"-XDide",
|
||||
"-XDkeepCommentsOverride=ignore",
|
||||
"-XDsuppressAbortOnBadClassFile",
|
||||
"-XDshould-stop.at=GENERATE",
|
||||
"-XDdiags.formatterOptions=-source",
|
||||
"-XDdiags.layout=%L%m|%L%m|%L%m",
|
||||
"-XDbreakDocCommentParsingOnError=false",
|
||||
"-Xlint:cast",
|
||||
"-Xlint:deprecation",
|
||||
"-Xlint:empty",
|
||||
"-Xlint:fallthrough",
|
||||
"-Xlint:finally",
|
||||
"-Xlint:path",
|
||||
"-Xlint:unchecked",
|
||||
"-Xlint:varargs",
|
||||
"-Xlint:static");
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
protected void setupCompileOptions(final ModuleProject module, final List<String> options) {
|
||||
if (module == null) {
|
||||
Collections.addAll(
|
||||
options,
|
||||
"-source",
|
||||
DEFAULT_COMPILER_SOURCE_AND_TARGET_VERSION,
|
||||
"-target",
|
||||
DEFAULT_COMPILER_SOURCE_AND_TARGET_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
final IJavaCompilerSettings compilerSettings = module.getCompilerSettings();
|
||||
options.add("-source");
|
||||
options.add(compilerSettings.getJavaSourceVersion());
|
||||
options.add("-target");
|
||||
options.add(compilerSettings.getJavaBytecodeVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the compilation failed because javac didn't find some package-private files in source files
|
||||
* with different names, list those source files.
|
||||
*/
|
||||
Set<Path> needsAdditionalSources() {
|
||||
|
||||
if (parent.getModule() == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// Check for "class not found errors" that refer to package private classes
|
||||
Set<Path> addFiles = new HashSet<>();
|
||||
for (Diagnostic<? extends JavaFileObject> err : parent.diagnostics) {
|
||||
if (!err.getCode().equals("compiler.err.cant.resolve.location")) {
|
||||
continue;
|
||||
}
|
||||
if (!isValidFileRange(err)) {
|
||||
continue;
|
||||
}
|
||||
String packageName = packageName(err);
|
||||
ClassTrie.Node node = parent.getModule().compileJavaSourceClasses.findNode(packageName);
|
||||
if (node != null && node.isClass() && node instanceof SourceClassTrie.SourceNode) {
|
||||
addFiles.add(((SourceClassTrie.SourceNode) node).getFile());
|
||||
}
|
||||
}
|
||||
return addFiles;
|
||||
}
|
||||
|
||||
private String packageName(Diagnostic<? extends JavaFileObject> err) {
|
||||
if (err instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) {
|
||||
JCDiagnostic diagnostic = ((ClientCodeWrapper.DiagnosticSourceUnwrapper) err).d;
|
||||
JCDiagnostic.DiagnosticPosition pos = diagnostic.getDiagnosticPosition();
|
||||
Object[] args = diagnostic.getArgs();
|
||||
Kinds.KindName kind = (Kinds.KindName) args[0];
|
||||
if (kind == Kinds.KindName.CLASS) {
|
||||
if (pos.toString().contains(".")) {
|
||||
return pos.toString().substring(0, pos.toString().lastIndexOf('.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Path file = Paths.get(err.getSource().toUri());
|
||||
return StringSearch.packageName(file);
|
||||
}
|
||||
|
||||
private boolean isValidFileRange(Diagnostic<? extends JavaFileObject> d) {
|
||||
return d.getSource().toUri().getScheme().equals("file")
|
||||
&& d.getStartPosition() >= 0
|
||||
&& d.getEndPosition() >= 0;
|
||||
}
|
||||
|
||||
public static class DiagnosticListenerWrapper extends DiagnosticListenerImpl {
|
||||
|
||||
private final Consumer<Diagnostic<? extends JavaFileObject>> consumer;
|
||||
|
||||
public DiagnosticListenerWrapper(
|
||||
final Consumer<Diagnostic<? extends JavaFileObject>> consumer, JavaFileObject jfo) {
|
||||
super(jfo);
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(final Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
consumer.accept(diagnostic);
|
||||
super.report(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.itsaky.androidide.javac.services.partial.DiagnosticListenerImpl;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.tools.javac.api.JavacTaskImpl;
|
||||
|
||||
public class CompileTask implements AutoCloseable {
|
||||
|
||||
public final JavacTaskImpl task;
|
||||
public final List<CompilationUnitTree> roots;
|
||||
public final List<Diagnostic<? extends JavaFileObject>> diagnostics;
|
||||
public final CompileBatch compileBatch;
|
||||
public final DiagnosticListenerImpl diagnosticListener;
|
||||
|
||||
public CompileTask(
|
||||
@NonNull CompileBatch compileBatch, List<Diagnostic<? extends JavaFileObject>> diagnostics) {
|
||||
this.compileBatch = compileBatch;
|
||||
this.task = compileBatch.task;
|
||||
this.roots = compileBatch.roots;
|
||||
this.diagnostics = diagnostics;
|
||||
this.diagnosticListener = compileBatch.diagnosticListener;
|
||||
}
|
||||
|
||||
public CompilationUnitTree root() {
|
||||
if (roots.size() != 1) {
|
||||
throw new RuntimeException("No compilation units found. Roots: " + roots.size());
|
||||
}
|
||||
return roots.get(0);
|
||||
}
|
||||
|
||||
public CompilationUnitTree root(Path file) {
|
||||
for (CompilationUnitTree root : roots) {
|
||||
if (root.getSourceFile().toUri().equals(file.toUri())) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Compilation unit not found");
|
||||
}
|
||||
|
||||
public CompilationUnitTree root(JavaFileObject file) {
|
||||
for (CompilationUnitTree root : roots) {
|
||||
if (root.getSourceFile().toUri().equals(file.toUri())) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Compilation unit not found");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
|
||||
public interface CompilerProvider {
|
||||
Path NOT_FOUND = Paths.get("");
|
||||
|
||||
TreeSet<String> publicTopLevelTypes();
|
||||
|
||||
TreeSet<String> packagePrivateTopLevelTypes(String packageName);
|
||||
|
||||
Optional<JavaFileObject> findAnywhere(String className);
|
||||
|
||||
Path findTypeDeclaration(String className);
|
||||
|
||||
Path[] findTypeReferences(String className);
|
||||
|
||||
Path[] findMemberReferences(String className, String memberName);
|
||||
|
||||
default List<String> findQualifiedNames(String simpleName) {
|
||||
return findQualifiedNames(simpleName, false);
|
||||
}
|
||||
|
||||
List<String> findQualifiedNames(String simpleName, boolean onlyOne);
|
||||
|
||||
ParseTask parse(Path file);
|
||||
|
||||
ParseTask parse(JavaFileObject file);
|
||||
|
||||
default SynchronizedTask compile(Path... files) {
|
||||
return compile(Arrays.stream(files).map(SourceFileObject::new).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
default SynchronizedTask compile(Collection<? extends JavaFileObject> sources) {
|
||||
return compile(new CompilationRequest(sources));
|
||||
}
|
||||
|
||||
SynchronizedTask compile(CompilationRequest request);
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler
|
||||
|
||||
import com.itsaky.androidide.utils.StopWatch
|
||||
import openjdk.source.tree.CompilationUnitTree
|
||||
import openjdk.tools.javac.api.JavacTaskImpl
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Default implementation of [CompilationTaskProcessor].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class DefaultCompilationTaskProcessor : CompilationTaskProcessor {
|
||||
|
||||
override fun process(task: JavacTaskImpl, processCompilationUnit: Consumer<CompilationUnitTree>) {
|
||||
val watch = StopWatch("Process compilation task")
|
||||
val trees = task.parse()
|
||||
watch.lapFromLast("Parsed treees")
|
||||
|
||||
trees.forEach(processCompilationUnit::accept)
|
||||
watch.lapFromLast("Processed trees")
|
||||
|
||||
// val entered = JavacTaskUtil.enterTrees(task, trees)
|
||||
// watch.lapFromLast("Entered trees")
|
||||
//
|
||||
// val analyzed = JavacTaskUtil.analyze(task, entered)
|
||||
task.analyze()
|
||||
watch.lapFromLast("Analyzed all trees")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableCompiler
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableContext
|
||||
|
||||
/**
|
||||
* Implementation of the [ReusableCompiler] which replaces necessary components from the
|
||||
* [ReusableContext].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class JCReusableCompiler : ReusableCompiler() {
|
||||
|
||||
override fun onCreateContext(): ReusableContext {
|
||||
return super.onCreateContext().also {
|
||||
JavaCompilerImpl.preRegister(context = it, replace = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler
|
||||
|
||||
import com.itsaky.androidide.models.Position
|
||||
import jdkx.tools.JavaFileObject
|
||||
import openjdk.tools.javac.util.Context
|
||||
|
||||
/**
|
||||
* Configuration for the [JavaCompilerImpl].
|
||||
*
|
||||
* @property files The main files that are being compiled/parsed.
|
||||
* @property completionInfo Information about the completion
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class JavaCompilerConfig(context: Context) {
|
||||
init {
|
||||
context.put(compilerConfigKey, this)
|
||||
}
|
||||
|
||||
var files: Collection<JavaFileObject>? = null
|
||||
var completionInfo: CompletionInfo? = null
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField val compilerConfigKey = Context.Key<JavaCompilerConfig>()
|
||||
|
||||
@JvmStatic
|
||||
fun instance(context: Context): JavaCompilerConfig {
|
||||
var instance = context.get(compilerConfigKey)
|
||||
if (instance == null) {
|
||||
instance = JavaCompilerConfig(context)
|
||||
}
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the completion request initiated by the Java completion provider.
|
||||
*
|
||||
* @property cursor The cursor position for the completion.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
data class CompletionInfo(val cursor: Position)
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler
|
||||
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableContext
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableJavaCompiler
|
||||
import com.itsaky.androidide.lsp.java.parser.ts.TSJavaParser
|
||||
import com.itsaky.androidide.lsp.java.parser.ts.TSMethodPruner.prune
|
||||
import com.itsaky.androidide.projects.FileManager
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
import com.itsaky.androidide.utils.withStopWatch
|
||||
import jdkx.tools.JavaFileObject
|
||||
import jdkx.tools.JavaFileObject.Kind.SOURCE
|
||||
import openjdk.tools.javac.api.ClientCodeWrapper
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit
|
||||
import openjdk.tools.javac.util.Context
|
||||
import kotlin.io.path.name
|
||||
|
||||
class JavaCompilerImpl(context: Context?) : ReusableJavaCompiler(context) {
|
||||
|
||||
override fun parse(filename: JavaFileObject?, content: CharSequence?): JCCompilationUnit {
|
||||
|
||||
if (VMUtils.isJvm()) {
|
||||
return super.parse(filename, content)
|
||||
}
|
||||
|
||||
val file = ClientCodeWrapper.instance(context).unwrap(filename)
|
||||
val compilerConfig = JavaCompilerConfig.instance(context)
|
||||
|
||||
// Preconditions
|
||||
if (
|
||||
content == null ||
|
||||
compilerConfig.files == null ||
|
||||
filename?.kind != SOURCE ||
|
||||
compilerConfig.files?.contains(file) == false
|
||||
) {
|
||||
return super.parse(filename, content)
|
||||
}
|
||||
|
||||
// If the file is NOT being parsed for a completion request,
|
||||
// we should not prune method bodies of active documents
|
||||
if (compilerConfig.completionInfo == null && FileManager.isActive(filename.toUri())) {
|
||||
return super.parse(filename, content)
|
||||
}
|
||||
|
||||
val pruned = withStopWatch("${if(file is SourceFileObject) "[${file.path.name}] " else ""}Prune method bodies") { watch ->
|
||||
val contentBuilder = StringBuilder(content)
|
||||
|
||||
return@withStopWatch TSJavaParser.parse(file).use { parseResult ->
|
||||
|
||||
prune(
|
||||
contentBuilder,
|
||||
parseResult.tree,
|
||||
compilerConfig.completionInfo?.cursor?.index ?: -1
|
||||
)
|
||||
|
||||
watch.log()
|
||||
|
||||
return@use contentBuilder
|
||||
}
|
||||
}
|
||||
|
||||
return super.parse(filename, pruned)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun preRegister(context: ReusableContext, replace: Boolean = false) {
|
||||
if (replace) {
|
||||
context.drop(compilerKey)
|
||||
}
|
||||
context.put(compilerKey, Context.Factory { JavaCompilerImpl(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
+576
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent;
|
||||
import com.itsaky.androidide.javac.services.compiler.ReusableCompiler;
|
||||
import com.itsaky.androidide.javac.services.partial.CompilationInfo;
|
||||
import com.itsaky.androidide.javac.services.partial.PartialReparser;
|
||||
import com.itsaky.androidide.javac.services.partial.PartialReparserImpl;
|
||||
import com.itsaky.androidide.lsp.java.models.CompilationRequest;
|
||||
import com.itsaky.androidide.lsp.java.models.PartialReparseRequest;
|
||||
import com.itsaky.androidide.lsp.java.parser.ParseTask;
|
||||
import com.itsaky.androidide.lsp.java.parser.Parser;
|
||||
import com.itsaky.androidide.lsp.java.utils.Extractors;
|
||||
import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarations;
|
||||
import com.itsaky.androidide.models.Position;
|
||||
import com.itsaky.androidide.models.Range;
|
||||
import com.itsaky.androidide.projects.FileManager;
|
||||
import com.itsaky.androidide.projects.api.AndroidModule;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.util.BootClasspathProvider;
|
||||
import com.itsaky.androidide.projects.util.StringSearch;
|
||||
import com.itsaky.androidide.utils.Cache;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.SourceClassTrie;
|
||||
import com.itsaky.androidide.utils.StopWatch;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import jdkx.tools.StandardLocation;
|
||||
import openjdk.source.tree.CompilationUnitTree;
|
||||
import openjdk.source.tree.MethodTree;
|
||||
import openjdk.source.util.SourcePositions;
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.source.util.Trees;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JavaCompilerService implements CompilerProvider {
|
||||
|
||||
public static final JavaCompilerService NO_MODULE_COMPILER = new JavaCompilerService(null);
|
||||
private static final Cache<String, Boolean> cacheContainsWord = new Cache<>();
|
||||
private static final Cache<Void, List<String>> cacheContainsType = new Cache<>();
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaCompilerService.class);
|
||||
protected final Set<String> classPathClasses;
|
||||
protected final List<Diagnostic<? extends JavaFileObject>> diagnostics = new ArrayList<>();
|
||||
protected final Map<JavaFileObject, Long> cachedModified = new HashMap<>();
|
||||
protected final Cache<Void, List<String>> cacheFileImports = new Cache<>();
|
||||
protected final SynchronizedTask synchronizedTask = new SynchronizedTask();
|
||||
protected final SourceFileManager fileManager;
|
||||
protected final ModuleProject module;
|
||||
public ReusableCompiler compiler = new JCReusableCompiler();
|
||||
protected Set<String> bootClasspathClasses =
|
||||
BootClasspathProvider.getTopLevelClasses(
|
||||
Collections.singleton(Environment.ANDROID_JAR.getAbsolutePath()));
|
||||
private CompileBatch cachedCompile;
|
||||
private int changeDelta = 0;
|
||||
|
||||
private Position lastReparsePosition = Position.NONE;
|
||||
private Position newCursorPosition = Position.NONE;
|
||||
|
||||
// The module project must not be null
|
||||
// It is marked as nullable just for some special cases like tests
|
||||
public JavaCompilerService(@Nullable ModuleProject module) {
|
||||
this.module = module;
|
||||
if (module == null) {
|
||||
this.fileManager = SourceFileManager.NO_MODULE;
|
||||
this.classPathClasses = Collections.emptySet();
|
||||
} else {
|
||||
this.fileManager = SourceFileManager.forModule(module);
|
||||
this.classPathClasses =
|
||||
Collections.unmodifiableSet(module.compileClasspathClasses.allClassNames());
|
||||
this.bootClasspathClasses = Collections.unmodifiableSet(getBootclasspathClasses());
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getBootclasspathClasses() {
|
||||
if (module != null && module instanceof AndroidModule) {
|
||||
final List<String> classpaths =
|
||||
((AndroidModule) module)
|
||||
.getBootClassPaths().stream().map(File::getPath).collect(Collectors.toList());
|
||||
BootClasspathProvider.update(classpaths);
|
||||
this.bootClasspathClasses =
|
||||
Collections.unmodifiableSet(BootClasspathProvider.getTopLevelClasses(classpaths));
|
||||
}
|
||||
return bootClasspathClasses;
|
||||
}
|
||||
|
||||
private JavaCompilerService(
|
||||
@Nullable ModuleProject module,
|
||||
SourceFileManager fileManager,
|
||||
Set<String> bootClasspathClasses,
|
||||
Set<String> classPathClasses) {
|
||||
this.module = module;
|
||||
this.fileManager = fileManager;
|
||||
this.bootClasspathClasses = bootClasspathClasses;
|
||||
this.classPathClasses = classPathClasses;
|
||||
}
|
||||
|
||||
public ModuleProject getModule() {
|
||||
return module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeSet<String> publicTopLevelTypes() {
|
||||
TreeSet<String> all = new TreeSet<>();
|
||||
List<SourceClassTrie.SourceNode> sourceClasses =
|
||||
module != null ? module.compileJavaSourceClasses.allSources() : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : sourceClasses) {
|
||||
all.add(node.getQualifiedName());
|
||||
}
|
||||
all.addAll(classPathClasses);
|
||||
all.addAll(bootClasspathClasses);
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeSet<String> packagePrivateTopLevelTypes(String packageName) {
|
||||
return new TreeSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<JavaFileObject> findAnywhere(String className) {
|
||||
Path fromSource = findTypeDeclaration(className);
|
||||
if (fromSource != NOT_FOUND) {
|
||||
return Optional.of(new SourceFileObject(fromSource));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path findTypeDeclaration(String className) {
|
||||
Path fastFind = findPublicTypeDeclaration(className);
|
||||
if (fastFind != NOT_FOUND) {
|
||||
return fastFind;
|
||||
}
|
||||
// In principle, the slow path can be skipped in many cases.
|
||||
// If we're spending a lot of time in findTypeDeclaration, this would be a good
|
||||
// optimization.
|
||||
String packageName = Extractors.packageName(className);
|
||||
String simpleName = Extractors.simpleName(className);
|
||||
List<SourceClassTrie.SourceNode> classes =
|
||||
module != null ? module.listClassesFromSourceDirs(packageName) : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : classes) {
|
||||
final Path path = node.getFile();
|
||||
if (containsWord(path, simpleName) && containsType(path, className)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path[] findTypeReferences(String className) {
|
||||
String packageName = Extractors.packageName(className);
|
||||
String simpleName = Extractors.simpleName(className);
|
||||
List<Path> candidates = new ArrayList<>();
|
||||
List<SourceClassTrie.SourceNode> sourceNodes =
|
||||
module != null ? module.compileJavaSourceClasses.allSources() : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : sourceNodes) {
|
||||
final Path path = node.getFile();
|
||||
if (containsWord(path, packageName)
|
||||
&& containsImport(path, className)
|
||||
&& containsWord(path, simpleName)) {
|
||||
candidates.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates.toArray(new Path[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path[] findMemberReferences(String className, String memberName) {
|
||||
List<Path> candidates = new ArrayList<>();
|
||||
List<SourceClassTrie.SourceNode> sourceNodes =
|
||||
module != null ? module.compileJavaSourceClasses.allSources() : Collections.emptyList();
|
||||
for (SourceClassTrie.SourceNode node : sourceNodes) {
|
||||
final Path path = node.getFile();
|
||||
if (containsWord(path, memberName)) {
|
||||
candidates.add(path);
|
||||
}
|
||||
}
|
||||
return candidates.toArray(new Path[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> findQualifiedNames(String simpleName, boolean onlyOne) {
|
||||
final var names = new ArrayList<String>();
|
||||
for (var name : publicTopLevelTypes()) {
|
||||
// This will be true in a test environment
|
||||
if (name.contains("/")) {
|
||||
name = name.replace('/', '.');
|
||||
}
|
||||
|
||||
if (name.endsWith("." + simpleName)) {
|
||||
names.add(name);
|
||||
if (onlyOne) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParseTask parse(Path file) {
|
||||
Parser parser = Parser.parseFile(file);
|
||||
return new ParseTask(parser.task, parser.root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParseTask parse(JavaFileObject file) {
|
||||
Parser parser = Parser.parseJavaFileObject(file);
|
||||
return new ParseTask(parser.task, parser.root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SynchronizedTask compile(final CompilationRequest request) {
|
||||
return compileBatch(request);
|
||||
}
|
||||
|
||||
private SynchronizedTask compileBatch(CompilationRequest request) {
|
||||
synchronizedTask.post(
|
||||
() -> {
|
||||
if (needsCompilation(request.sources)) {
|
||||
reparseOrRecompile(request);
|
||||
} else {
|
||||
LOG.info("...using cached compile");
|
||||
}
|
||||
synchronizedTask.setTask(new CompileTask(cachedCompile, diagnostics));
|
||||
});
|
||||
|
||||
return synchronizedTask;
|
||||
}
|
||||
|
||||
private boolean needsCompilation(Collection<? extends JavaFileObject> sources) {
|
||||
if (cachedModified.size() != sources.size()) {
|
||||
return true;
|
||||
}
|
||||
for (JavaFileObject f : sources) {
|
||||
if (!cachedModified.containsKey(f)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Long modified = cachedModified.get(f);
|
||||
if (modified != null && f.getLastModified() != modified) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private synchronized void reparseOrRecompile(CompilationRequest request) {
|
||||
// if (needsRecompilation(request)) {
|
||||
// LOG.warn("Cannot reparse. Recompilation is required");
|
||||
recompile(request);
|
||||
// } else {
|
||||
// LOG.debug("Trying to perform a reparse...");
|
||||
// tryReparse(request);
|
||||
// }
|
||||
}
|
||||
|
||||
private boolean needsRecompilation(final CompilationRequest request) {
|
||||
return this.cachedCompile == null
|
||||
|| this.cachedCompile.closed;
|
||||
// || request.partialRequest == null
|
||||
// || request.partialRequest.cursor < 0
|
||||
// || !isChangeValidForReparse()
|
||||
// || request.sources.size() != 1; // Cannot perform a reparse if there are multiple files
|
||||
}
|
||||
|
||||
private boolean isChangeValidForReparse() {
|
||||
return this.lastReparsePosition == Position.NONE
|
||||
|| (this.newCursorPosition != Position.NONE
|
||||
&& this.lastReparsePosition.getLine() == this.newCursorPosition.getLine());
|
||||
}
|
||||
|
||||
private void tryReparse(@NonNull final CompilationRequest request) {
|
||||
|
||||
// Satisfy lint
|
||||
final PartialReparseRequest partialRequest = request.partialRequest;
|
||||
Objects.requireNonNull(partialRequest);
|
||||
|
||||
final StopWatch watch = new StopWatch("Method reparse");
|
||||
final File file = new File(request.sources.iterator().next().toUri());
|
||||
final String path = file.getAbsolutePath();
|
||||
final List<Pair<Range, TreePath>> positions = this.cachedCompile.methodPositions.get(path);
|
||||
if (positions == null) {
|
||||
LOG.warn("Cannot perform reparse. No method positions found.");
|
||||
recompile(request);
|
||||
return;
|
||||
}
|
||||
|
||||
final Pair<Range, TreePath> currentMethod =
|
||||
binarySearchCurrentMethod(positions, partialRequest.cursor);
|
||||
if (currentMethod == null) {
|
||||
LOG.warn("Cannot perform reparse. Unable to find current method");
|
||||
recompile(request);
|
||||
return;
|
||||
} else {
|
||||
watch.lapFromLast("Found method at cursor position");
|
||||
}
|
||||
|
||||
final MethodTree methodTree = (MethodTree) currentMethod.second.getLeaf();
|
||||
LOG.debug("Trying to reparse method: {}", methodTree.getName());
|
||||
|
||||
final CompilationInfo info =
|
||||
new CompilationInfo(
|
||||
cachedCompile.task, cachedCompile.diagnosticListener, cachedCompile.roots.get(0));
|
||||
watch.setLastLap(System.currentTimeMillis());
|
||||
final SourcePositions sourcePositions = Trees.instance(cachedCompile.task).getSourcePositions();
|
||||
final int start = (int) sourcePositions.getStartPosition(info.cu, methodTree.getBody());
|
||||
final int end =
|
||||
(int) sourcePositions.getEndPosition(info.cu, methodTree.getBody()) + this.changeDelta;
|
||||
|
||||
if (start < 0 || end < 0 || start > end || end >= partialRequest.contents.length()) {
|
||||
LOG.warn(
|
||||
"Cannot reparse. Invalid change delta. end: {} changeDelta: {} content.length: {}",
|
||||
end,
|
||||
this.changeDelta,
|
||||
partialRequest.contents.length()
|
||||
);
|
||||
recompile(request);
|
||||
return;
|
||||
}
|
||||
|
||||
watch.lapFromLast("Found start and end positions of current method");
|
||||
final PartialReparser reparser = new PartialReparserImpl();
|
||||
|
||||
final String newBody = partialRequest.contents.substring(start, end);
|
||||
final boolean reparsed =
|
||||
reparser.reparseMethod(info, currentMethod.second, newBody, partialRequest.contents);
|
||||
if (!reparsed) {
|
||||
LOG.error("Failed to reparse");
|
||||
recompile(request);
|
||||
return;
|
||||
}
|
||||
|
||||
watch.log();
|
||||
LOG.info("Successfully reparsed method: {}", methodTree.getName());
|
||||
updateModificationCache(request);
|
||||
cachedCompile.updatePositions(info.cu, true);
|
||||
this.changeDelta = 0;
|
||||
this.lastReparsePosition = this.newCursorPosition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<Range, TreePath> binarySearchCurrentMethod(
|
||||
@NonNull final List<Pair<Range, TreePath>> positions, final long cursor) {
|
||||
int left = 0;
|
||||
int right = positions.size() - 1;
|
||||
while (left <= right) {
|
||||
int mid = (left + right) / 2;
|
||||
final Pair<Range, TreePath> method = positions.get(mid);
|
||||
final Range range = method.first;
|
||||
final int startIndex = range.getStart().requireIndex();
|
||||
final int endIndex = range.getEnd().requireIndex();
|
||||
|
||||
if (cursor < startIndex) {
|
||||
right = mid - 1;
|
||||
} else if (cursor > endIndex) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized void recompile(CompilationRequest request) {
|
||||
close();
|
||||
this.cachedCompile = performCompilation(request);
|
||||
this.changeDelta = 0;
|
||||
updateModificationCache(request);
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if (cachedCompile != null) {
|
||||
cachedCompile.close();
|
||||
cachedCompile.borrow.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateModificationCache(final CompilationRequest request) {
|
||||
cachedModified.clear();
|
||||
for (JavaFileObject f : request.sources) {
|
||||
cachedModified.put(f, f.getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
private CompileBatch performCompilation(CompilationRequest request) {
|
||||
final Collection<? extends JavaFileObject> sources = request.sources;
|
||||
if (sources.isEmpty()) {
|
||||
throw new RuntimeException("empty sources");
|
||||
}
|
||||
|
||||
CompileBatch firstAttempt = new CompileBatch(this, sources, request);
|
||||
Set<Path> addFiles = firstAttempt.needsAdditionalSources();
|
||||
|
||||
if (addFiles.isEmpty()) {
|
||||
return firstAttempt;
|
||||
}
|
||||
|
||||
// If the compiler needs additional source files that contain package-private files
|
||||
LOG.info("...need to recompile with {}", addFiles);
|
||||
firstAttempt.close();
|
||||
firstAttempt.borrow.close();
|
||||
|
||||
List<JavaFileObject> moreSources = new ArrayList<>(sources);
|
||||
for (Path add : addFiles) {
|
||||
moreSources.add(new SourceFileObject(add));
|
||||
}
|
||||
|
||||
return new CompileBatch(this, moreSources, request);
|
||||
}
|
||||
|
||||
private boolean containsWord(Path file, String word) {
|
||||
if (cacheContainsWord.needs(file, word)) {
|
||||
cacheContainsWord.load(file, word, StringSearch.containsWord(file, word));
|
||||
}
|
||||
return cacheContainsWord.get(file, word);
|
||||
}
|
||||
|
||||
private boolean containsImport(Path file, String className) {
|
||||
String packageName = Extractors.packageName(className);
|
||||
if (packageNameOrEmpty(file).equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
String star = packageName + ".*";
|
||||
for (String i : readImports(file)) {
|
||||
if (i.equals(className) || i.equals(star)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<String> readImports(Path file) {
|
||||
if (cacheFileImports.needs(file, null)) {
|
||||
loadImports(file);
|
||||
}
|
||||
return cacheFileImports.get(file, null);
|
||||
}
|
||||
|
||||
private void loadImports(Path file) {
|
||||
List<String> list = new ArrayList<>();
|
||||
Pattern importClass = Pattern.compile("^import +([\\w.]+\\.\\w+);");
|
||||
Pattern importStar = Pattern.compile("^import +([\\w.]+\\.\\*);");
|
||||
try (BufferedReader lines = FileManager.INSTANCE.getReader(file)) {
|
||||
for (String line = lines.readLine(); line != null; line = lines.readLine()) {
|
||||
// If we reach a class declaration, stop looking for imports
|
||||
// TODO This could be a little more specific
|
||||
if (line.contains("class")) {
|
||||
break;
|
||||
}
|
||||
// import foo.bar.Doh;
|
||||
Matcher matchesClass = importClass.matcher(line);
|
||||
if (matchesClass.matches()) {
|
||||
list.add(matchesClass.group(1));
|
||||
}
|
||||
// import foo.bar.*
|
||||
Matcher matchesStar = importStar.matcher(line);
|
||||
if (matchesStar.matches()) {
|
||||
list.add(matchesStar.group(1));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
cacheFileImports.load(file, null, list);
|
||||
}
|
||||
|
||||
private String packageNameOrEmpty(Path file) {
|
||||
return module != null ? module.packageNameOrEmpty(file) : "";
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
synchronizedTask.post(
|
||||
() -> {
|
||||
close();
|
||||
cachedCompile = null;
|
||||
cachedModified.clear();
|
||||
compiler = new ReusableCompiler();
|
||||
});
|
||||
}
|
||||
|
||||
public SynchronizedTask getSynchronizedTask() {
|
||||
return synchronizedTask;
|
||||
}
|
||||
|
||||
public void onDocumentChange(@NonNull DocumentChangeEvent event) {
|
||||
this.changeDelta += event.getChangeDelta();
|
||||
this.newCursorPosition = event.getChangeRange().getEnd();
|
||||
}
|
||||
|
||||
public JavaCompilerService copy() {
|
||||
final JavaCompilerService compiler =
|
||||
new JavaCompilerService(
|
||||
this.module, this.fileManager, this.bootClasspathClasses, this.classPathClasses);
|
||||
compiler.cachedCompile = null;
|
||||
compiler.newCursorPosition = Position.NONE;
|
||||
compiler.lastReparsePosition = Position.NONE;
|
||||
compiler.changeDelta = 0;
|
||||
compiler.compiler = new ReusableCompiler();
|
||||
compiler.diagnostics.clear();
|
||||
compiler.cachedModified.clear();
|
||||
return compiler;
|
||||
}
|
||||
|
||||
private boolean containsType(Path file, String className) {
|
||||
if (cacheContainsType.needs(file, null)) {
|
||||
CompilationUnitTree root = parse(file).root;
|
||||
List<String> types = new ArrayList<>();
|
||||
new FindTypeDeclarations().scan(root, types);
|
||||
cacheContainsType.load(file, null, types);
|
||||
}
|
||||
return cacheContainsType.get(file, null).contains(className);
|
||||
}
|
||||
|
||||
private Path findPublicTypeDeclaration(String className) {
|
||||
JavaFileObject source;
|
||||
try {
|
||||
source =
|
||||
fileManager.getJavaFileForInput(
|
||||
StandardLocation.SOURCE_PATH, className, JavaFileObject.Kind.SOURCE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (source == null) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
if (!source.toUri().getScheme().equals("file")) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
Path file = Paths.get(source.toUri());
|
||||
if (!containsType(file, className)) {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.blankj.utilcode.util.CloseUtils;
|
||||
import com.itsaky.androidide.javac.config.JavacConfigProvider;
|
||||
import com.itsaky.androidide.javac.services.fs.AndroidFsProviderImpl;
|
||||
import com.itsaky.androidide.projects.api.AndroidModule;
|
||||
import com.itsaky.androidide.projects.api.ModuleProject;
|
||||
import com.itsaky.androidide.projects.util.StringSearch;
|
||||
import com.itsaky.androidide.utils.ClassTrie;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.SourceClassTrie;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
import jdkx.tools.FileObject;
|
||||
import jdkx.tools.ForwardingJavaFileManager;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import jdkx.tools.StandardLocation;
|
||||
import openjdk.tools.javac.api.JavacTool;
|
||||
import openjdk.tools.javac.file.JavacFileManager;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SourceFileManager extends ForwardingJavaFileManager<JavacFileManager> {
|
||||
|
||||
public static final SourceFileManager NO_MODULE;
|
||||
private static final Logger LOG;
|
||||
private static final Map<ModuleProject, SourceFileManager> cachedFileManagers =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
// Initialize LOG first to prevent NPE
|
||||
LOG = LoggerFactory.getLogger(SourceFileManager.class);
|
||||
NO_MODULE = new SourceFileManager(null);
|
||||
}
|
||||
|
||||
private final ModuleProject module;
|
||||
|
||||
private SourceFileManager(final ModuleProject module) {
|
||||
super(createDelegateFileManager());
|
||||
this.module = module;
|
||||
|
||||
AndroidFsProviderImpl.INSTANCE.init();
|
||||
|
||||
if (module == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be set before setting classpaths
|
||||
System.setProperty(JavacConfigProvider.PROP_ANDROIDIDE_JAVA_HOME,
|
||||
Environment.JAVA_HOME.getAbsolutePath());
|
||||
|
||||
setLocationLogError(StandardLocation.SOURCE_PATH, module.getCompileSourceDirectories());
|
||||
setLocationLogError(StandardLocation.CLASS_PATH, configureClasspaths(module));
|
||||
listLocations(EnumSet.of(StandardLocation.CLASS_PATH, StandardLocation.PLATFORM_CLASS_PATH));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Set<File> configureClasspaths(final ModuleProject module) {
|
||||
if (module == null) {
|
||||
return emptySet();
|
||||
}
|
||||
|
||||
if (module instanceof AndroidModule) {
|
||||
final AndroidModule androidModule = (AndroidModule) module;
|
||||
setLocationLogError(StandardLocation.PLATFORM_CLASS_PATH, androidModule.getBootClassPaths());
|
||||
}
|
||||
|
||||
return module.getCompileClasspaths();
|
||||
}
|
||||
|
||||
private static JavacFileManager createDelegateFileManager() {
|
||||
return JavacTool.create()
|
||||
.getStandardFileManager(msg -> LOG.debug(String.valueOf(msg)), Locale.getDefault(),
|
||||
StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public void setLocationLogError(Location location, Iterable<File> searchPath) {
|
||||
try {
|
||||
fileManager.setLocation(location, searchPath);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unable to set location", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void listLocations(final EnumSet<StandardLocation> locations) {
|
||||
for (StandardLocation location : locations) {
|
||||
try {
|
||||
fileManager.cacheLocation(location);
|
||||
} catch (Exception e) {
|
||||
// Ignored
|
||||
LOG.debug("Failed to list location: {}", location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<JavaFileObject> list(
|
||||
Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse)
|
||||
throws IOException {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
if (this.module == null) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
final ClassTrie.Node node = module.compileJavaSourceClasses.findNode(packageName);
|
||||
if (node == null || node.isClass()) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
final Stream<ClassTrie.Node> nodes;
|
||||
if (recurse) {
|
||||
nodes = node.allClassNodes().stream();
|
||||
} else {
|
||||
nodes = node.getChildren().values().stream();
|
||||
}
|
||||
|
||||
final Stream<JavaFileObject> stream =
|
||||
nodes
|
||||
.filter(it -> it instanceof SourceClassTrie.SourceNode)
|
||||
.map(it -> asJavaFileObject((SourceClassTrie.SourceNode) it));
|
||||
|
||||
return stream::iterator;
|
||||
}
|
||||
|
||||
return super.list(location, packageName, kinds, recurse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inferBinaryName(Location location, JavaFileObject file) {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
SourceFileObject source = (SourceFileObject) file;
|
||||
String packageName = packageNameOrEmpty(source.path);
|
||||
String className = removeExtension(source.path.getFileName().toString());
|
||||
if (!packageName.isEmpty()) {
|
||||
className = packageName + "." + className;
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
return super.inferBinaryName(location, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLocation(Location location) {
|
||||
return location == StandardLocation.SOURCE_PATH || super.hasLocation(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForInput(
|
||||
Location location, String className, JavaFileObject.Kind kind) throws IOException {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
String packageName = StringSearch.mostName(className);
|
||||
String simpleClassName = StringSearch.lastName(className);
|
||||
List<SourceClassTrie.SourceNode> classes =
|
||||
module != null ? module.listClassesFromSourceDirs(packageName) : emptyList();
|
||||
for (SourceClassTrie.SourceNode node : classes) {
|
||||
final Path path = node.getFile();
|
||||
if (path.getFileName().toString().equals(simpleClassName + kind.extension)) {
|
||||
return new SourceFileObject(path);
|
||||
}
|
||||
}
|
||||
// Fall through to disk in case we have .jar or .zip files on the source path
|
||||
}
|
||||
return super.getJavaFileForInput(location, className, kind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileObject getFileForInput(Location location, String packageName, String relativeName)
|
||||
throws IOException {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
return null;
|
||||
}
|
||||
return super.getFileForInput(location, packageName, relativeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Location location, FileObject file) throws IOException {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
SourceFileObject source = (SourceFileObject) file;
|
||||
return module.compileJavaSourceClasses.findSource(source.path) != null;
|
||||
} else {
|
||||
return super.contains(location, file);
|
||||
}
|
||||
}
|
||||
|
||||
private String packageNameOrEmpty(Path file) {
|
||||
return this.module != null ? module.packageNameOrEmpty(file) : "";
|
||||
}
|
||||
|
||||
private String removeExtension(String fileName) {
|
||||
int lastDot = fileName.lastIndexOf(".");
|
||||
return (lastDot == -1 ? fileName : fileName.substring(0, lastDot));
|
||||
}
|
||||
|
||||
private JavaFileObject asJavaFileObject(SourceClassTrie.SourceNode node) {
|
||||
// TODO erase method bodies of files that are not open
|
||||
return new SourceFileObject(node.getFile());
|
||||
}
|
||||
|
||||
public static SourceFileManager forModule(@NonNull ModuleProject project) {
|
||||
Objects.requireNonNull(project);
|
||||
return cachedFileManagers.computeIfAbsent(project, SourceFileManager::createForModule);
|
||||
}
|
||||
|
||||
private static SourceFileManager createForModule(@NonNull ModuleProject project) {
|
||||
LOG.info("Creating source file manager instance for module: {}", project);
|
||||
return new SourceFileManager(project);
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
for (final SourceFileManager fileManager : cachedFileManagers.values()) {
|
||||
CloseUtils.closeIO(fileManager);
|
||||
}
|
||||
|
||||
cachedFileManagers.clear();
|
||||
}
|
||||
|
||||
public void setContext(final Context context) {
|
||||
fileManager.setContext(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.compiler;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.itsaky.androidide.projects.FileManager;
|
||||
import com.itsaky.androidide.utils.DocumentUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import jdkx.lang.model.element.Modifier;
|
||||
import jdkx.lang.model.element.NestingKind;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
|
||||
public class SourceFileObject implements JavaFileObject {
|
||||
/** path is the absolute path to this file on disk */
|
||||
final Path path;
|
||||
/** contents is the text in this file, or null if we should use the text in FileStore */
|
||||
String contents;
|
||||
/** if contents is set, the modified time of contents */
|
||||
Instant modified;
|
||||
|
||||
public SourceFileObject(Path path) {
|
||||
this(path, null, Instant.EPOCH);
|
||||
}
|
||||
|
||||
public SourceFileObject(Path path, String contents, Instant modified) {
|
||||
if (!DocumentUtils.isJavaFile(path)) throw new RuntimeException(path + " is not a java source");
|
||||
this.path = path;
|
||||
this.contents = contents;
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("path", this.path.toString()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
String name = path.getFileName().toString();
|
||||
return kindFromExtension(name);
|
||||
}
|
||||
|
||||
private static Kind kindFromExtension(String name) {
|
||||
for (Kind candidate : Kind.values()) {
|
||||
if (name.endsWith(candidate.extension)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
return path.getFileName().toString().equals(simpleName + kind.extension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
return this.path.toAbsolutePath().toUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() {
|
||||
if (contents != null) {
|
||||
byte[] bytes = contents.getBytes();
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
return FileManager.INSTANCE.getInputStream(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) {
|
||||
if (contents != null) {
|
||||
return new StringReader(contents);
|
||||
}
|
||||
return FileManager.INSTANCE.getReader(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
if (contents != null) {
|
||||
return contents;
|
||||
}
|
||||
return FileManager.INSTANCE.getDocumentContents(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
if (contents != null) {
|
||||
return modified.toEpochMilli();
|
||||
}
|
||||
return FileManager.INSTANCE.getLastModified(this.path).toEpochMilli();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof SourceFileObject)) {
|
||||
return false;
|
||||
}
|
||||
final SourceFileObject that = (SourceFileObject) o;
|
||||
try {
|
||||
return this.path != null && that.path != null
|
||||
&& Files.isSameFile(this.path, that.path)
|
||||
&& Objects.equals(contents, that.contents)
|
||||
&& Objects.equals(modified, that.modified);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(path, contents, modified);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.lsp.java.compiler
|
||||
|
||||
import com.itsaky.androidide.lsp.java.CompilationCancellationException
|
||||
import com.itsaky.androidide.lsp.java.utils.CancelChecker.Companion.isCancelled
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.Semaphore
|
||||
|
||||
class SynchronizedTask {
|
||||
|
||||
@Volatile
|
||||
@PublishedApi
|
||||
internal var isCompiling = false
|
||||
|
||||
@PublishedApi
|
||||
internal val semaphore = Semaphore(1)
|
||||
|
||||
@PublishedApi
|
||||
internal var task: CompileTask? = null
|
||||
private set
|
||||
|
||||
companion object {
|
||||
|
||||
@PublishedApi
|
||||
internal val log = LoggerFactory.getLogger(SynchronizedTask::class.java)
|
||||
}
|
||||
|
||||
inline fun run(crossinline taskConsumer: (CompileTask) -> Unit) {
|
||||
try {
|
||||
semaphore.acquire()
|
||||
} catch (e: InterruptedException) {
|
||||
throw CompilationCancellationException(e)
|
||||
}
|
||||
try {
|
||||
taskConsumer(task!!)
|
||||
} catch (err: Throwable) {
|
||||
if (!isCancelled(err)) {
|
||||
log.error("An error occurred while working with compilation task", err)
|
||||
}
|
||||
throw err
|
||||
} finally {
|
||||
semaphore.release()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T : Any?> get(crossinline action: (CompileTask) -> T): T {
|
||||
try {
|
||||
semaphore.acquire()
|
||||
} catch (e: InterruptedException) {
|
||||
throw CompilationCancellationException(e)
|
||||
}
|
||||
return try {
|
||||
action(task!!)
|
||||
} catch (err: Throwable) {
|
||||
if (!isCancelled(err)) {
|
||||
log.error("An error occurred while working with compilation task", err)
|
||||
}
|
||||
throw err
|
||||
} finally {
|
||||
semaphore.release()
|
||||
}
|
||||
}
|
||||
|
||||
fun post(action: Runnable) = post { action.run() }
|
||||
|
||||
inline fun post(action: () -> Unit) {
|
||||
try {
|
||||
semaphore.acquire()
|
||||
} catch (e: InterruptedException) {
|
||||
throw CompilationCancellationException(e)
|
||||
}
|
||||
isCompiling = true
|
||||
try {
|
||||
if (task != null) {
|
||||
task!!.close()
|
||||
}
|
||||
action()
|
||||
} catch (err: Throwable) {
|
||||
if (!isCancelled(err)) {
|
||||
log.error("An error occurred", err)
|
||||
}
|
||||
throw err
|
||||
} finally {
|
||||
semaphore.release()
|
||||
isCompiling = false
|
||||
}
|
||||
}
|
||||
|
||||
fun setTask(task: CompileTask?) {
|
||||
this.task = task
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
val isBusy: Boolean
|
||||
get() = isCompiling || semaphore.availablePermits() == 0
|
||||
|
||||
/**
|
||||
* **FOR INTERNAL USE ONLY!**
|
||||
*/
|
||||
fun logStats() {
|
||||
log.warn("[SynchronizedTask] isCompiling={} queuedLength={}", isCompiling,
|
||||
semaphore.queueLength)
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.edits
|
||||
|
||||
import com.itsaky.androidide.lsp.java.JavaCompilerProvider
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Handles edits for Java completion items.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class AdvancedJavaEditHandler(protected val file: Path) : BaseJavaEditHandler() {
|
||||
|
||||
override fun performEdits(
|
||||
item: CompletionItem,
|
||||
editor: CodeEditor,
|
||||
text: Content,
|
||||
line: Int,
|
||||
column: Int,
|
||||
index: Int
|
||||
) {
|
||||
val compiler = JavaCompilerProvider.get(
|
||||
IProjectManager.getInstance().findModuleForFile(file, false) ?: return)
|
||||
performEdits(compiler, editor, item)
|
||||
|
||||
executeCommand(editor, item.command)
|
||||
}
|
||||
|
||||
/**
|
||||
* Java edit handlers which require instance of the compiler should override this method instead
|
||||
* of [performEdits].
|
||||
*
|
||||
* @param compiler The compiler service instance.
|
||||
* @param editor The editor to perform edits on.
|
||||
* @param completionItem The completion item which contains required data.
|
||||
*/
|
||||
abstract fun performEdits(
|
||||
compiler: JavaCompilerService,
|
||||
editor: CodeEditor,
|
||||
completionItem: CompletionItem
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.edits
|
||||
|
||||
import com.itsaky.androidide.editor.api.ILspEditor
|
||||
import com.itsaky.androidide.lsp.edits.DefaultEditHandler
|
||||
import com.itsaky.androidide.lsp.models.Command
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
|
||||
/**
|
||||
* Implementation of [DefaultEditHandler] which avoids reflection in
|
||||
* [DefaultEditHandler.executeCommand].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class BaseJavaEditHandler : DefaultEditHandler() {
|
||||
|
||||
override fun executeCommand(editor: CodeEditor, command: Command?) {
|
||||
if (editor is ILspEditor) {
|
||||
editor.executeCommand(command)
|
||||
return
|
||||
}
|
||||
super.executeCommand(editor, command)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.edits
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import com.itsaky.androidide.lsp.models.ClassCompletionData
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
import com.itsaky.androidide.lsp.util.RewriteHelper
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Imports the required class for a ClassCompletionItem.
|
||||
*
|
||||
* @param imports The current file imports.
|
||||
* @param file The file in which this edit will be performed.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ClassImportEditHandler(val imports: Set<String>, file: Path) : AdvancedJavaEditHandler(file) {
|
||||
|
||||
override fun performEdits(
|
||||
compiler: JavaCompilerService,
|
||||
editor: CodeEditor,
|
||||
completionItem: CompletionItem
|
||||
) {
|
||||
val data = completionItem.data as? ClassCompletionData ?: return
|
||||
val className = data.className
|
||||
val edits = EditHelper.addImportIfNeeded(compiler, file, imports, className)
|
||||
|
||||
if (edits.isNotEmpty()) {
|
||||
RewriteHelper.performEdits(edits, editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.edits
|
||||
|
||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||
import com.itsaky.androidide.lsp.java.utils.EditHelper
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Imports multiple classes at once.
|
||||
*
|
||||
* @param classes The fully qualified classnames to import.
|
||||
* @param imported The current imports of the given file.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class MultipleClassImportEditHandler(
|
||||
private val classes: Set<String>,
|
||||
private val imported: Set<String>,
|
||||
file: Path
|
||||
) : AdvancedJavaEditHandler(file) {
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(MultipleClassImportEditHandler::class.java)
|
||||
}
|
||||
|
||||
override fun performEdits(
|
||||
compiler: JavaCompilerService,
|
||||
editor: CodeEditor,
|
||||
completionItem: com.itsaky.androidide.lsp.models.CompletionItem
|
||||
) {
|
||||
val edits = mutableListOf<com.itsaky.androidide.lsp.models.TextEdit>()
|
||||
for (className in classes) {
|
||||
try {
|
||||
edits.addAll(EditHelper.addImportIfNeeded(compiler, file, imported, className))
|
||||
} catch (err: Throwable) {
|
||||
log.error("Unable to compute edits to perform import for class: {}", className)
|
||||
}
|
||||
}
|
||||
com.itsaky.androidide.lsp.util.RewriteHelper.performEdits(edits, editor)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
/**
|
||||
* Anything that can be cloned.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ICloneable {
|
||||
fun clone(): ICloneable
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user