mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
compound indices in action
This commit is contained in:
+112
-165
@@ -22,8 +22,6 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.containers.MostlySingularMultiMap;
|
||||
import com.intellij.util.io.DataInputOutputUtil;
|
||||
import com.intellij.util.io.KeyDescriptor;
|
||||
import com.intellij.util.io.PersistentEnumeratorDelegate;
|
||||
@@ -39,7 +37,6 @@ import java.io.DataOutput;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -53,7 +50,6 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
return ApplicationManager.getApplication().getComponent(BytecodeAnalysisConverter.class);
|
||||
}
|
||||
|
||||
private PersistentStringEnumerator myInternalKeyEnumerator;
|
||||
private PersistentStringEnumerator myPackageEnumerator;
|
||||
private PersistentStringEnumerator myNamesEnumerator;
|
||||
private PersistentEnumeratorDelegate<int[]> myCompoundKeyEnumerator;
|
||||
@@ -62,11 +58,9 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
public void initComponent() {
|
||||
try {
|
||||
File keysDir = new File(PathManager.getIndexRoot(), "bytecodeKeys");
|
||||
final File internalKeysFile = new File(keysDir, "faba.internalIds");
|
||||
final File packageKeysFile = new File(keysDir, "faba.packages");
|
||||
final File namesFile = new File(keysDir, "faba.names1");
|
||||
final File compoundKeysFile = new File(keysDir, "faba.keys");
|
||||
myInternalKeyEnumerator = new PersistentStringEnumerator(internalKeysFile);
|
||||
final File packageKeysFile = new File(keysDir, "packages");
|
||||
final File namesFile = new File(keysDir, "names");
|
||||
final File compoundKeysFile = new File(keysDir, "compound");
|
||||
myPackageEnumerator = new PersistentStringEnumerator(packageKeysFile);
|
||||
myNamesEnumerator = new PersistentStringEnumerator(namesFile);
|
||||
myCompoundKeyEnumerator = new PersistentEnumeratorDelegate<int[]>(compoundKeysFile, new IntArrayKeyDescriptor(), 1024 * 4);
|
||||
@@ -79,7 +73,6 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
@Override
|
||||
public void disposeComponent() {
|
||||
try {
|
||||
myInternalKeyEnumerator.close();
|
||||
myPackageEnumerator.close();
|
||||
myNamesEnumerator.close();
|
||||
myCompoundKeyEnumerator.close();
|
||||
@@ -109,8 +102,8 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
int[] ids = new int[keyComponent.size()];
|
||||
int idI = 0;
|
||||
for (Key id : keyComponent) {
|
||||
// TODO refactor here
|
||||
ids[idI] = myInternalKeyEnumerator.enumerate(internalKeyString(id));
|
||||
int[] compoundKey = mkCompoundKey(id);
|
||||
ids[idI] = myCompoundKeyEnumerator.enumerate(compoundKey);
|
||||
idI++;
|
||||
}
|
||||
IntIdComponent intIdComponent = new IntIdComponent(ids);
|
||||
@@ -119,26 +112,17 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
}
|
||||
result = new IntIdPending(pending.infinum, components);
|
||||
}
|
||||
int key = myInternalKeyEnumerator.enumerate(internalKeyString(equation.id));
|
||||
|
||||
int key = myCompoundKeyEnumerator.enumerate(mkCompoundKey(equation.id));
|
||||
return new IntIdEquation(key, result);
|
||||
}
|
||||
|
||||
public static class InternalKey {
|
||||
final String annotationKey;
|
||||
final Direction dir;
|
||||
|
||||
InternalKey(String annotationKey, Direction dir) {
|
||||
this.annotationKey = annotationKey;
|
||||
this.dir = dir;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public int[] mkCompoundKey(@NotNull Key key) throws IOException {
|
||||
Direction direction = key.direction;
|
||||
Method method = key.method;
|
||||
|
||||
Type ownerType = Type.getType(method.internalClassName);
|
||||
Type ownerType = Type.getObjectType(method.internalClassName);
|
||||
Type[] argTypes = Type.getArgumentTypes(method.methodDesc);
|
||||
Type returnType = Type.getReturnType(method.methodDesc);
|
||||
|
||||
@@ -159,10 +143,23 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
return compoundKey;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Direction extractDirection(int[] compoundKey) {
|
||||
switch (compoundKey[0]) {
|
||||
case Direction.OUT_DIRECTION:
|
||||
return new Out();
|
||||
case Direction.IN_DIRECTION:
|
||||
return new In(compoundKey[1]);
|
||||
case Direction.INOUT_DIRECTION:
|
||||
return new InOut(compoundKey[1], Value.values()[compoundKey[2]]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void writeType(int[] compoundKey, int i, Type type) throws IOException {
|
||||
String className = type.getClassName();
|
||||
int dotIndex = className.lastIndexOf('.');
|
||||
|
||||
String packageName;
|
||||
String simpleName;
|
||||
if (dotIndex > 0) {
|
||||
@@ -172,44 +169,12 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
packageName = "";
|
||||
simpleName = className;
|
||||
}
|
||||
|
||||
compoundKey[i] = myPackageEnumerator.enumerate(packageName);
|
||||
compoundKey[i + 1] = myNamesEnumerator.enumerate(simpleName);
|
||||
myPackageEnumerator.valueOf(compoundKey[i]);
|
||||
myNamesEnumerator.valueOf(compoundKey[i + 1]);
|
||||
}
|
||||
|
||||
public String showCompoundKey(@NotNull int[] key) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(key[0]);
|
||||
sb.append(", ");
|
||||
sb.append(key[1]);
|
||||
sb.append(", ");
|
||||
sb.append(key[2]);
|
||||
sb.append(", ");
|
||||
sb.append(myPackageEnumerator.valueOf(key[3]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[4]));
|
||||
sb.append(", ");
|
||||
sb.append(myPackageEnumerator.valueOf(key[5]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[6]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[7]));
|
||||
sb.append(", ");
|
||||
sb.append(key[8]);
|
||||
sb.append(", ");
|
||||
|
||||
for (int i = 0; i < key[8]; i++) {
|
||||
sb.append(myPackageEnumerator.valueOf(key[9 + 2*i]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[9 + 2*i + 1]));
|
||||
sb.append(", ");
|
||||
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public int[] mkCompoundKey(@NotNull PsiMethod psiMethod, Direction direction) throws IOException {
|
||||
|
||||
@@ -256,6 +221,16 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
return compoundKey;
|
||||
}
|
||||
|
||||
public int mkKey(@NotNull PsiMethod psiMethod, Direction direction) throws IOException {
|
||||
int[] compoundKey = mkCompoundKey(psiMethod, direction);
|
||||
if (compoundKey == null) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return myCompoundKeyEnumerator.enumerate(compoundKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(int[] compoundKey, int i, PsiClass psiClass, int dimensions) throws IOException {
|
||||
String packageName = "";
|
||||
|
||||
@@ -290,6 +265,7 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
}
|
||||
|
||||
if (psiType instanceof PsiClassType) {
|
||||
// no resolve() -> no package/class split
|
||||
PsiClass psiClass = ((PsiClassType)psiType).resolve();
|
||||
if (psiClass != null) {
|
||||
writeClass(compoundKey, i, psiClass, dimensions);
|
||||
@@ -303,71 +279,88 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
String packageName = "";
|
||||
String className = psiType.getPresentableText();
|
||||
compoundKey[i] = myPackageEnumerator.enumerate(packageName);
|
||||
compoundKey[i + 1] = myNamesEnumerator.enumerate(className);
|
||||
if (dimensions == 0) {
|
||||
compoundKey[i + 1] = myNamesEnumerator.enumerate(className);
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder(className);
|
||||
for (int j = 0; j < dimensions; j++) {
|
||||
sb.append("[]");
|
||||
}
|
||||
compoundKey[i + 1] = myNamesEnumerator.enumerate(sb.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public MostlySingularMultiMap<String, AnnotationData> makeAnnotations(TIntObjectHashMap<Value> internalIdSolutions) {
|
||||
MostlySingularMultiMap<String, AnnotationData> annotations = new MostlySingularMultiMap<String, AnnotationData>();
|
||||
HashMap<String, StringBuilder> contracts = new HashMap<String, StringBuilder>();
|
||||
TIntObjectIterator<Value> iterator = internalIdSolutions.iterator();
|
||||
public Annotations makeAnnotations(TIntObjectHashMap<Value> internalIdSolutions) {
|
||||
final TIntObjectHashMap<AnnotationData> outs = new TIntObjectHashMap<AnnotationData>();
|
||||
final TIntObjectHashMap<AnnotationData> params = new TIntObjectHashMap<AnnotationData>();
|
||||
final TIntObjectHashMap<AnnotationData> contracts = new TIntObjectHashMap<AnnotationData>();
|
||||
|
||||
TIntObjectHashMap<StringBuilder> contractBuilders = new TIntObjectHashMap<StringBuilder>();
|
||||
TIntObjectIterator<Value> solutionsIterator = internalIdSolutions.iterator();
|
||||
|
||||
for (int i = internalIdSolutions.size(); i-- > 0;) {
|
||||
iterator.advance();
|
||||
int inKey = iterator.key();
|
||||
Value value = iterator.value();
|
||||
solutionsIterator.advance();
|
||||
int key = solutionsIterator.key();
|
||||
Value value = solutionsIterator.value();
|
||||
if (value == Value.Top || value == Value.Bot) {
|
||||
continue;
|
||||
}
|
||||
InternalKey key;
|
||||
int[] compoundKey = null;
|
||||
try {
|
||||
String s = myInternalKeyEnumerator.valueOf(inKey);
|
||||
key = readInternalKey(s);
|
||||
compoundKey = myCompoundKeyEnumerator.valueOf(key);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
// TODO: question: how to react?
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
Direction direction = key.dir;
|
||||
String baseAnnKey = key.annotationKey;
|
||||
if (compoundKey != null) {
|
||||
Direction direction = extractDirection(compoundKey);
|
||||
|
||||
if (direction instanceof In && value == Value.NotNull) {
|
||||
String annKey = baseAnnKey + " " + ((In)direction).paramIndex;
|
||||
// TODO - here
|
||||
annotations.add(annKey, new AnnotationData("org.jetbrains.annotations.NotNull", ""));
|
||||
params.put(key, new AnnotationData("org.jetbrains.annotations.NotNull", ""));
|
||||
}
|
||||
else if (direction instanceof Out && value == Value.NotNull) {
|
||||
// TODO - here
|
||||
annotations.add(baseAnnKey, new AnnotationData("org.jetbrains.annotations.NotNull", ""));
|
||||
outs.put(key, new AnnotationData("org.jetbrains.annotations.NotNull", ""));
|
||||
}
|
||||
// TODO - sort (normalize) contract clauses
|
||||
else if (direction instanceof InOut) {
|
||||
StringBuilder sb = contracts.get(baseAnnKey);
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder("\"");
|
||||
contracts.put(baseAnnKey, sb);
|
||||
compoundKey[0] = 0;
|
||||
compoundKey[1] = 0;
|
||||
compoundKey[2] = 0;
|
||||
try {
|
||||
// TODO - sort (normalize) contract clauses
|
||||
int baseKey = myCompoundKeyEnumerator.enumerate(compoundKey);
|
||||
StringBuilder sb = contractBuilders.get(baseKey);
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder("\"");
|
||||
contractBuilders.put(baseKey, sb);
|
||||
}
|
||||
else {
|
||||
sb.append(';');
|
||||
}
|
||||
int arity = compoundKey[8];
|
||||
contractElement(sb, arity, (InOut)direction, value);
|
||||
}
|
||||
else {
|
||||
sb.append(';');
|
||||
catch (IOException e) {
|
||||
// TODO: question: how to react?
|
||||
}
|
||||
contractElement(sb, calculateArity(baseAnnKey), (InOut)direction, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, StringBuilder> contract : contracts.entrySet()) {
|
||||
if (!annotations.containsKey(contract.getKey())) {
|
||||
annotations.add(contract.getKey(), new AnnotationData("org.jetbrains.annotations.Contract", contract.getValue().append('"').toString()));
|
||||
TIntObjectIterator<StringBuilder> buildersIterator = contractBuilders.iterator();
|
||||
for (int i = contractBuilders.size(); i-- > 0;) {
|
||||
buildersIterator.advance();
|
||||
int key = buildersIterator.key();
|
||||
StringBuilder value = buildersIterator.value();
|
||||
if (!outs.contains(key)) {
|
||||
contracts.put(key, new AnnotationData("org.jetbrains.annotations.Contract", value.append('"').toString()));
|
||||
}
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
// TODO - this is a hack for now
|
||||
static int calculateArity(String annotationKey) {
|
||||
return annotationKey.split(",").length;
|
||||
return new Annotations(outs, params, contracts);
|
||||
}
|
||||
|
||||
static String contractValueString(Value v) {
|
||||
@@ -396,79 +389,34 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String internalKeyString(Key key) {
|
||||
return annotationKey(key.method) + ';' + direction2Key(key.direction);
|
||||
}
|
||||
public String debugCompoundKey(@NotNull int[] key) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(key[0]);
|
||||
sb.append(", ");
|
||||
sb.append(key[1]);
|
||||
sb.append(", ");
|
||||
sb.append(key[2]);
|
||||
sb.append(", ");
|
||||
sb.append(myPackageEnumerator.valueOf(key[3]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[4]));
|
||||
sb.append(", ");
|
||||
sb.append(myPackageEnumerator.valueOf(key[5]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[6]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[7]));
|
||||
sb.append(", ");
|
||||
sb.append(key[8]);
|
||||
sb.append(", ");
|
||||
|
||||
for (int i = 0; i < key[8]; i++) {
|
||||
sb.append(myPackageEnumerator.valueOf(key[9 + 2*i]));
|
||||
sb.append(", ");
|
||||
sb.append(myNamesEnumerator.valueOf(key[9 + 2*i + 1]));
|
||||
sb.append(", ");
|
||||
|
||||
public static String direction2Key(Direction dir) {
|
||||
if (dir instanceof In) {
|
||||
return "In:" + ((In)dir).paramIndex;
|
||||
} else if (dir instanceof Out) {
|
||||
return "Out";
|
||||
} else {
|
||||
InOut inOut = (InOut)dir;
|
||||
return "InOut:" + inOut.paramIndex + ":" + inOut.inValue.name();
|
||||
}
|
||||
}
|
||||
|
||||
public static InternalKey readInternalKey(String s) {
|
||||
String[] parts = s.split(";");
|
||||
String annKey = parts[0];
|
||||
String[] dirStrings = parts[1].split(":");
|
||||
if ("In".equals(dirStrings[0])) {
|
||||
return new InternalKey(annKey, new In(Integer.valueOf(dirStrings[1])));
|
||||
} else if ("Out".equals(dirStrings[0])) {
|
||||
return new InternalKey(annKey, new Out());
|
||||
} else {
|
||||
return new InternalKey(annKey, new InOut(Integer.valueOf(dirStrings[1]), Value.valueOf(dirStrings[2])));
|
||||
}
|
||||
}
|
||||
|
||||
public static String annotationKey(Method method) {
|
||||
if ("<init>".equals(method.methodName)) {
|
||||
return canonical(method.internalClassName) + " " +
|
||||
simpleName(method.internalClassName) +
|
||||
parameters(method);
|
||||
} else {
|
||||
return canonical(method.internalClassName) + " " +
|
||||
returnType(method) + " " +
|
||||
method.methodName +
|
||||
parameters(method);
|
||||
}
|
||||
}
|
||||
|
||||
private static String returnType(Method method) {
|
||||
return canonical(Type.getReturnType(method.methodDesc).getClassName());
|
||||
}
|
||||
|
||||
public static String canonical(String internalName) {
|
||||
return internalName.replace('/', '.').replace('$', '.');
|
||||
}
|
||||
|
||||
private static String simpleName(String internalName) {
|
||||
String cn = canonical(internalName);
|
||||
int lastDotIndex = cn.lastIndexOf('.');
|
||||
if (lastDotIndex == -1) {
|
||||
return cn;
|
||||
} else {
|
||||
return cn.substring(lastDotIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static String parameters(Method method) {
|
||||
Type[] argTypes = Type.getArgumentTypes(method.methodDesc);
|
||||
StringBuilder sb = new StringBuilder("(");
|
||||
boolean notFirst = false;
|
||||
for (Type argType : argTypes) {
|
||||
if (notFirst) {
|
||||
sb.append(", ");
|
||||
}
|
||||
else {
|
||||
notFirst = true;
|
||||
}
|
||||
sb.append(canonical(argType.getClassName()));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -503,5 +451,4 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+49
-41
@@ -28,24 +28,20 @@ import com.intellij.openapi.startup.StartupManager;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.PsiModifierListOwner;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.*;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MostlySingularMultiMap;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import gnu.trove.TIntObjectHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -53,28 +49,25 @@ import java.util.List;
|
||||
public class ProjectBytecodeAnalysis extends AbstractProjectComponent {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis");
|
||||
private static final List<AnnotationData> NO_DATA = new ArrayList<AnnotationData>(1);
|
||||
private static final List<AnnotationData> NO_DATA = new ArrayList<AnnotationData>(0);
|
||||
|
||||
private final PsiManager myPsiManager;
|
||||
|
||||
private MostlySingularMultiMap<String, AnnotationData> myAnnotations = null;
|
||||
private Annotations myAnnotations = null;
|
||||
|
||||
public ProjectBytecodeAnalysis(Project project, PsiManager psiManager) {
|
||||
super(project);
|
||||
myPsiManager = psiManager;
|
||||
|
||||
// TODO: question: what is a proper way to handle indices changes?
|
||||
StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//doIndex();
|
||||
}
|
||||
public void run() {}
|
||||
});
|
||||
final MessageBusConnection connection = myProject.getMessageBus().connect();
|
||||
connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
|
||||
@Override
|
||||
public void rootsChanged(ModuleRootEvent event) {
|
||||
//doIndex();
|
||||
}
|
||||
public void rootsChanged(ModuleRootEvent event) {}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,24 +88,18 @@ public class ProjectBytecodeAnalysis extends AbstractProjectComponent {
|
||||
TIntObjectHashMap<Value> solutions = solver.solve();
|
||||
LOG.info("equations are solved");
|
||||
myAnnotations = BytecodeAnalysisConverter.getInstance().makeAnnotations(solutions);
|
||||
LOG.info("initialized " + myAnnotations.size());
|
||||
LOG.info("initialized ");
|
||||
}
|
||||
|
||||
|
||||
// TODO: what follows was just copied/modified from BaseExternalAnnotationsManager
|
||||
// TODO: refactor?
|
||||
@Nullable
|
||||
public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) {
|
||||
String key = getExternalName(listOwner);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
List<AnnotationData> list = collectInferredAnnotations(listOwner);
|
||||
AnnotationData data = findByFQN(list, annotationFQN);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
LOG.info("annotation: " + key + " " + data);
|
||||
return data.getAnnotation(this);
|
||||
}
|
||||
|
||||
@@ -120,15 +107,12 @@ public class ProjectBytecodeAnalysis extends AbstractProjectComponent {
|
||||
public PsiAnnotation[] findInferredAnnotations(@NotNull PsiModifierListOwner listOwner) {
|
||||
List<AnnotationData> result = collectInferredAnnotations(listOwner);
|
||||
if (result == null || result.isEmpty()) return null;
|
||||
PsiAnnotation[] myResult = ContainerUtil.map2Array(result, PsiAnnotation.EMPTY_ARRAY, new Function<AnnotationData, PsiAnnotation>() {
|
||||
return ContainerUtil.map2Array(result, PsiAnnotation.EMPTY_ARRAY, new Function<AnnotationData, PsiAnnotation>() {
|
||||
@Override
|
||||
public PsiAnnotation fun(AnnotationData data) {
|
||||
return data.getAnnotation(ProjectBytecodeAnalysis.this);
|
||||
}
|
||||
});
|
||||
String key = getExternalName(listOwner);
|
||||
LOG.info("annotations: " + key + " " + result);
|
||||
return myResult;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -142,28 +126,40 @@ public class ProjectBytecodeAnalysis extends AbstractProjectComponent {
|
||||
}
|
||||
|
||||
private synchronized List<AnnotationData> collectInferredAnnotations(PsiModifierListOwner listOwner) {
|
||||
String key = getExternalName(listOwner);
|
||||
if (key == null) {
|
||||
return NO_DATA;
|
||||
}
|
||||
SmartList<AnnotationData> result = new SmartList<AnnotationData>();
|
||||
|
||||
if (myAnnotations == null) {
|
||||
loadAnnotations();
|
||||
}
|
||||
|
||||
Iterable<AnnotationData> inferred = myAnnotations.get(key);
|
||||
ContainerUtil.addAll(result, inferred);
|
||||
return result;
|
||||
try {
|
||||
int key = getKey(listOwner);
|
||||
if (key == -1) {
|
||||
return NO_DATA;
|
||||
}
|
||||
SmartList<AnnotationData> result = new SmartList<AnnotationData>();
|
||||
ContainerUtil.addIfNotNull(result, myAnnotations.contracts.get(key));
|
||||
ContainerUtil.addIfNotNull(result, myAnnotations.outs.get(key));
|
||||
ContainerUtil.addIfNotNull(result, myAnnotations.params.get(key));
|
||||
return result;
|
||||
}
|
||||
catch (IOException e) {
|
||||
return NO_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static String getExternalName(@NotNull PsiModifierListOwner listOwner) {
|
||||
String rawExternalName = PsiFormatUtil.getRawExternalName(listOwner);
|
||||
if (rawExternalName != null) {
|
||||
rawExternalName = rawExternalName.replace("...)", "[])");
|
||||
protected static int getKey(@NotNull PsiModifierListOwner owner) throws IOException {
|
||||
if (owner instanceof PsiMethod) {
|
||||
return BytecodeAnalysisConverter.getInstance().mkKey((PsiMethod)owner, new Out());
|
||||
}
|
||||
else if (owner instanceof PsiParameter) {
|
||||
final PsiElement declarationScope = ((PsiParameter)owner).getDeclarationScope();
|
||||
if (!(declarationScope instanceof PsiMethod)) {
|
||||
return -1;
|
||||
}
|
||||
final PsiMethod psiMethod = (PsiMethod)declarationScope;
|
||||
final int index = psiMethod.getParameterList().getParameterIndex((PsiParameter)owner);
|
||||
return BytecodeAnalysisConverter.getInstance().mkKey(psiMethod, new In(index));
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
return rawExternalName;
|
||||
}
|
||||
|
||||
// interner for storing annotation FQN
|
||||
@@ -190,6 +186,18 @@ public class ProjectBytecodeAnalysis extends AbstractProjectComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class Annotations {
|
||||
final TIntObjectHashMap<AnnotationData> outs;
|
||||
final TIntObjectHashMap<AnnotationData> params;
|
||||
final TIntObjectHashMap<AnnotationData> contracts;
|
||||
|
||||
Annotations(TIntObjectHashMap<AnnotationData> outs, TIntObjectHashMap<AnnotationData> params, TIntObjectHashMap<AnnotationData> contracts) {
|
||||
this.outs = outs;
|
||||
this.params = params;
|
||||
this.contracts = contracts;
|
||||
}
|
||||
}
|
||||
|
||||
class AnnotationData {
|
||||
@NotNull final String annotationClassFqName;
|
||||
@NotNull final String annotationParameters;
|
||||
|
||||
+22
-23
@@ -18,9 +18,9 @@ package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.InferredAnnotationsManager;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.data.Test01;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.data.Test02;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.data.Test03;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.data.TestConverterData;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.data.TestAnnotation;
|
||||
import com.intellij.openapi.application.ex.PathManagerEx;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -29,6 +29,7 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.apache.commons.lang.SystemUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.junit.Assert;
|
||||
@@ -57,6 +58,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
myBytecodeAnalysisConverter = BytecodeAnalysisConverter.getInstance();
|
||||
|
||||
setUpDataClasses();
|
||||
setUpVelocityLibrary();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,32 +67,31 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
// TODO - test it, possible solution - via external annotation manager??
|
||||
/*
|
||||
// TODO: real integration test (possible solution - via external annotation manager??)
|
||||
public void testVelocityJar() {
|
||||
VirtualFile lib = LocalFileSystem.getInstance().refreshAndFindFileByPath(PathManagerEx.getTestDataPath() + "/../../../lib");
|
||||
assertNotNull(lib);
|
||||
PsiTestUtil.addLibrary(myModule, "velocity", lib.getPath(), new String[]{"/velocity.jar!/"}, new String[]{});
|
||||
PsiClass psiClass = myJavaPsiFacade.findClass(SystemUtils.class.getName(), GlobalSearchScope.moduleWithLibrariesScope(myModule));
|
||||
assertNotNull(psiClass);
|
||||
PsiMethod getJavaHomeMethod = psiClass.findMethodsByName("getJavaHome", false)[0];
|
||||
assertNotNull(InferredAnnotationsManager.getInstance(myModule.getProject()).findInferredAnnotation(getJavaHomeMethod, AnnotationUtil.NOT_NULL));
|
||||
}
|
||||
*/
|
||||
|
||||
private void setUpVelocityLibrary() {
|
||||
VirtualFile lib = LocalFileSystem.getInstance().refreshAndFindFileByPath(PathManagerEx.getTestDataPath() + "/../../../lib");
|
||||
assertNotNull(lib);
|
||||
PsiTestUtil.addLibrary(myModule, "velocity", lib.getPath(), new String[]{"/velocity.jar!/"}, new String[]{});
|
||||
}
|
||||
|
||||
|
||||
public void testInference() throws IOException {
|
||||
checkAnnotations(Test01.class);
|
||||
}
|
||||
|
||||
public void testCompoundKeys() throws IOException {
|
||||
public void testConverter() throws IOException {
|
||||
checkCompoundIds(Test01.class);
|
||||
checkCompoundIds(Test02.class);
|
||||
checkCompoundIds(Test02.Inner1.class);
|
||||
checkCompoundIds(Test02.Inner2.class);
|
||||
checkCompoundIds(Test03.class);
|
||||
checkCompoundIds(Test03.Inner1.class);
|
||||
checkCompoundIds(Test03.Inner2.class);
|
||||
checkCompoundIds(TestConverterData.class);
|
||||
checkCompoundIds(TestConverterData.StaticNestedClass.class);
|
||||
checkCompoundIds(TestConverterData.InnerClass.class);
|
||||
checkCompoundIds(TestConverterData.GenericStaticNestedClass.class);
|
||||
checkCompoundIds(TestAnnotation.class);
|
||||
}
|
||||
|
||||
@@ -107,8 +108,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
Annotation[] parameterAnnotations = annotations[i];
|
||||
PsiParameter psiParameter = psiMethod.getParameterList().getParameters()[i];
|
||||
PsiAnnotation inferredAnnotation = myInferredAnnotationsManager.findInferredAnnotation(psiParameter, AnnotationUtil.NOT_NULL);
|
||||
for (int j = 0; j < parameterAnnotations.length; j++) {
|
||||
Annotation parameterAnnotation = parameterAnnotations[j];
|
||||
for (Annotation parameterAnnotation : parameterAnnotations) {
|
||||
if (parameterAnnotation.annotationType() == ExpectNotNull.class) {
|
||||
assertNotNull(inferredAnnotation);
|
||||
continue params;
|
||||
@@ -128,7 +128,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
|
||||
assertEquals(expectedContract == null, actualContractAnnotation == null);
|
||||
|
||||
if (expectedAnnotation != null) {
|
||||
if (expectedContract != null) {
|
||||
String expectedContractValue = expectedContract.value();
|
||||
String actualContractValue = AnnotationUtil.getStringAttributeValue(actualContractAnnotation, null);
|
||||
assertEquals(expectedContractValue, actualContractValue);
|
||||
@@ -144,14 +144,14 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
|
||||
for (java.lang.reflect.Method javaMethod : javaClass.getDeclaredMethods()) {
|
||||
System.out.println(javaMethod.getName());
|
||||
Method method = new Method(Type.getDescriptor(javaClass), javaMethod.getName(), Type.getMethodDescriptor(javaMethod));
|
||||
Method method = new Method(Type.getType(javaClass).getInternalName(), javaMethod.getName(), Type.getMethodDescriptor(javaMethod));
|
||||
boolean noKey = javaMethod.getAnnotation(ExpectNoPsiKey.class) != null;
|
||||
PsiMethod psiMethod = psiClass.findMethodsByName(javaMethod.getName(), false)[0];
|
||||
checkCompoundId(method, psiMethod, noKey);
|
||||
}
|
||||
|
||||
for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
|
||||
Method method = new Method(Type.getDescriptor(javaClass), "<init>", Type.getConstructorDescriptor(constructor));
|
||||
Method method = new Method(Type.getType(javaClass).getInternalName(), "<init>", Type.getConstructorDescriptor(constructor));
|
||||
boolean noKey = constructor.getAnnotation(ExpectNoPsiKey.class) != null;
|
||||
PsiMethod[] constructors = psiClass.getConstructors();
|
||||
PsiMethod psiMethod = constructors[0];
|
||||
@@ -175,8 +175,8 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
System.out.println(Arrays.toString(asmKey));
|
||||
System.out.println(Arrays.toString(psiKey));
|
||||
|
||||
System.out.println(myBytecodeAnalysisConverter.showCompoundKey(asmKey));
|
||||
System.out.println(myBytecodeAnalysisConverter.showCompoundKey(psiKey));
|
||||
System.out.println(myBytecodeAnalysisConverter.debugCompoundKey(asmKey));
|
||||
System.out.println(myBytecodeAnalysisConverter.debugCompoundKey(psiKey));
|
||||
|
||||
Assert.assertArrayEquals(asmKey, psiKey);
|
||||
}
|
||||
@@ -187,7 +187,6 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
FileUtil.copyDir(classesDir, destDir);
|
||||
VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(destDir);
|
||||
assertNotNull(vFile);
|
||||
|
||||
PsiTestUtil.addLibrary(myModule, "dataClasses", vFile.getPath(), new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed 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.intellij.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ExpectNoPsiKey;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class Test02 {
|
||||
|
||||
public static class Inner1 {
|
||||
public Inner1(Object o) {
|
||||
|
||||
}
|
||||
public Inner1[] Inner1test01(Inner1[] tests, Inner1... ellipsis) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
|
||||
public class Inner2 {
|
||||
public Inner2(Object o) {
|
||||
|
||||
}
|
||||
public Inner2[] Inner2test01(Inner2[] tests, Inner2... ellipsis) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
|
||||
public Test02(int x) {}
|
||||
|
||||
@ExpectNoPsiKey
|
||||
public BytecodeAnalysisConverter.InternalKey test01(BytecodeAnalysisConverter.InternalKey converter) {
|
||||
return converter;
|
||||
}
|
||||
|
||||
public Test02[] test02(Test02[] tests) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed 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.intellij.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ExpectNoPsiKey;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class Test03<A> {
|
||||
|
||||
public static class Inner1 {
|
||||
public Inner1(Object o) {
|
||||
|
||||
}
|
||||
public Inner1[] Inner1test01(Inner1[] tests, Inner1... ellipsis) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
|
||||
public class Inner2 {
|
||||
public Inner2(A o) {
|
||||
|
||||
}
|
||||
public A[] Inner2test01(A[] tests, A... ellipsis) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
|
||||
public Test03(@TestAnnotation int x) {}
|
||||
|
||||
@ExpectNoPsiKey
|
||||
public BytecodeAnalysisConverter.InternalKey test01(BytecodeAnalysisConverter.InternalKey converter) {
|
||||
return converter;
|
||||
}
|
||||
|
||||
public Test03[] test02(Test03[] tests) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed 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.intellij.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ExpectNoPsiKey;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class TestConverterData {
|
||||
|
||||
public static class StaticNestedClass {
|
||||
public StaticNestedClass(Object o) {
|
||||
|
||||
}
|
||||
public StaticNestedClass[] test01(StaticNestedClass[] ns, StaticNestedClass... ellipsis) {
|
||||
return ns;
|
||||
}
|
||||
}
|
||||
|
||||
public class InnerClass {
|
||||
// a reference to outer class should be inserted when translating PSI -> ASM
|
||||
public InnerClass(Object o) {}
|
||||
|
||||
public InnerClass[] Inner2test01(InnerClass[] tests, InnerClass... ellipsis) {
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GenericStaticNestedClass<A> {
|
||||
public GenericStaticNestedClass(A a) {
|
||||
|
||||
}
|
||||
public GenericStaticNestedClass[] test01(GenericStaticNestedClass[] ns, GenericStaticNestedClass... ellipsis) {
|
||||
return ns;
|
||||
}
|
||||
|
||||
public GenericStaticNestedClass<A>[] test02(GenericStaticNestedClass<A>[] ns, GenericStaticNestedClass<A>... ellipsis) {
|
||||
return ns;
|
||||
}
|
||||
|
||||
public class GenericInnerClass<B> {
|
||||
public GenericInnerClass(B b) {}
|
||||
|
||||
public <C> GenericStaticNestedClass<A> test01(GenericInnerClass<C> c) {
|
||||
return GenericStaticNestedClass.this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TestConverterData(int x) {}
|
||||
|
||||
// BytecodeAnalysisConverter class is not in the project path, so translation from PSI is impossible
|
||||
@ExpectNoPsiKey
|
||||
public BytecodeAnalysisConverter test01(BytecodeAnalysisConverter converter) {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@TestAnnotation
|
||||
public TestConverterData[] test02(@TestAnnotation TestConverterData[] tests) throws Exception {
|
||||
return tests;
|
||||
}
|
||||
|
||||
public boolean[] test03(boolean[] b) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user