initial implementation for compound keys

This commit is contained in:
Ilya Klyuchnikov
2014-07-10 10:35:36 +02:00
committed by peter
parent d905c85c97
commit ff8433736f
7 changed files with 532 additions and 17 deletions
@@ -19,16 +19,26 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.ApplicationComponent;
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;
import com.intellij.util.io.PersistentStringEnumerator;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectIterator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.Type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
@@ -43,14 +53,23 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
return ApplicationManager.getApplication().getComponent(BytecodeAnalysisConverter.class);
}
PersistentStringEnumerator internalKeyEnumerator;
private PersistentStringEnumerator myInternalKeyEnumerator;
private PersistentStringEnumerator myPackageEnumerator;
private PersistentStringEnumerator myNamesEnumerator;
private PersistentEnumeratorDelegate<int[]> myCompoundKeyEnumerator;
@Override
public void initComponent() {
try {
File dir = new File(PathManager.getIndexRoot(), "bytecodeKeys");
final File internalKeysFile = new File(dir, "faba.internalIds");
internalKeyEnumerator = new PersistentStringEnumerator(internalKeysFile);
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);
myPackageEnumerator = new PersistentStringEnumerator(packageKeysFile);
myNamesEnumerator = new PersistentStringEnumerator(namesFile);
myCompoundKeyEnumerator = new PersistentEnumeratorDelegate<int[]>(compoundKeysFile, new IntArrayKeyDescriptor(), 1024 * 4);
}
catch (IOException e) {
LOG.error(e);
@@ -60,7 +79,10 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
@Override
public void disposeComponent() {
try {
internalKeyEnumerator.close();
myInternalKeyEnumerator.close();
myPackageEnumerator.close();
myNamesEnumerator.close();
myCompoundKeyEnumerator.close();
}
catch (IOException e) {
LOG.error(e);
@@ -88,7 +110,7 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
int idI = 0;
for (Key id : keyComponent) {
// TODO refactor here
ids[idI] = internalKeyEnumerator.enumerate(internalKeyString(id));
ids[idI] = myInternalKeyEnumerator.enumerate(internalKeyString(id));
idI++;
}
IntIdComponent intIdComponent = new IntIdComponent(ids);
@@ -97,11 +119,11 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
}
result = new IntIdPending(pending.infinum, components);
}
int key = internalKeyEnumerator.enumerate(internalKeyString(equation.id));
int key = myInternalKeyEnumerator.enumerate(internalKeyString(equation.id));
return new IntIdEquation(key, result);
}
static class InternalKey {
public static class InternalKey {
final String annotationKey;
final Direction dir;
@@ -111,6 +133,182 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
}
}
@NotNull
public int[] mkCompoundKey(@NotNull Key key) throws IOException {
Direction direction = key.direction;
Method method = key.method;
Type ownerType = Type.getType(method.internalClassName);
Type[] argTypes = Type.getArgumentTypes(method.methodDesc);
Type returnType = Type.getReturnType(method.methodDesc);
int arity = argTypes.length;
int[] compoundKey = new int[9 + arity * 2];
compoundKey[0] = direction.directionId();
compoundKey[1] = direction.paramId();
compoundKey[2] = direction.valueId();
writeType(compoundKey, 3, ownerType);
writeType(compoundKey, 5, returnType);
compoundKey[7] = myNamesEnumerator.enumerate(method.methodName);
compoundKey[8] = argTypes.length;
for (int i = 0; i < argTypes.length; i++) {
writeType(compoundKey, 9 + 2*i, argTypes[i]);
}
return compoundKey;
}
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) {
packageName = className.substring(0, dotIndex);
simpleName = className.substring(dotIndex + 1);
} else {
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 {
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
if (psiClass == null) {
return null;
}
PsiClass outerClass = psiClass.getContainingClass();
boolean isInnerClassConstructor = psiMethod.isConstructor() && (outerClass != null) && !psiClass.hasModifierProperty(PsiModifier.STATIC);
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
final int shift = isInnerClassConstructor ? 1 : 0;
final int arity = parameters.length + shift;
int[] compoundKey = new int[9 + arity * 2];
compoundKey[0] = direction.directionId();
compoundKey[1] = direction.paramId();
compoundKey[2] = direction.valueId();
writeClass(compoundKey, 3, psiClass, 0);
PsiType returnType = psiMethod.getReturnType();
if (returnType == null) {
if (!writeType(compoundKey, 5, PsiType.VOID)) {
return null;
}
compoundKey[7] = myNamesEnumerator.enumerate("<init>");
} else {
if (!writeType(compoundKey, 5, returnType)) {
return null;
}
compoundKey[7] = myNamesEnumerator.enumerate(psiMethod.getName());
}
compoundKey[8] = arity;
if (isInnerClassConstructor) {
writeClass(compoundKey, 9, outerClass, 0);
}
for (int i = 0; i < parameters.length; i++) {
PsiParameter parameter = parameters[i];
if (!writeType(compoundKey, 9 + (2 * (i + shift)), parameter.getType())) {
return null;
}
}
return compoundKey;
}
private void writeClass(int[] compoundKey, int i, PsiClass psiClass, int dimensions) throws IOException {
String packageName = "";
PsiClassOwner psiFile = (PsiClassOwner) psiClass.getContainingFile();
if (psiFile != null) {
packageName = psiFile.getPackageName();
}
String qname = psiClass.getQualifiedName();
String className = qname;
if (qname != null && packageName.length() > 0) {
className = qname.substring(packageName.length() + 1).replace('.', '$');
}
compoundKey[i] = myPackageEnumerator.enumerate(packageName);
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());
}
}
private boolean writeType(int[] compoundKey, int i, PsiType psiType) throws IOException {
int dimensions = 0;
psiType = TypeConversionUtil.erasure(psiType);
if (psiType instanceof PsiArrayType) {
PsiArrayType arrayType = (PsiArrayType)psiType;
psiType = arrayType.getDeepComponentType();
dimensions = arrayType.getArrayDimensions();
}
if (psiType instanceof PsiClassType) {
PsiClass psiClass = ((PsiClassType)psiType).resolve();
if (psiClass != null) {
writeClass(compoundKey, i, psiClass, dimensions);
return true;
}
else {
return false;
}
}
else if (psiType instanceof PsiPrimitiveType) {
String packageName = "";
String className = psiType.getPresentableText();
compoundKey[i] = myPackageEnumerator.enumerate(packageName);
compoundKey[i + 1] = myNamesEnumerator.enumerate(className);
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>();
@@ -124,7 +322,7 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
}
InternalKey key;
try {
String s = internalKeyEnumerator.valueOf(inKey);
String s = myInternalKeyEnumerator.valueOf(inKey);
key = readInternalKey(s);
}
catch (IOException e) {
@@ -273,4 +471,37 @@ public class BytecodeAnalysisConverter implements ApplicationComponent {
sb.append(')');
return sb.toString();
}
private static class IntArrayKeyDescriptor implements KeyDescriptor<int[]> {
@Override
public void save(@NotNull DataOutput out, int[] value) throws IOException {
DataInputOutputUtil.writeINT(out, value.length);
for (int i : value) {
DataInputOutputUtil.writeINT(out, i);
}
}
@Override
public int[] read(@NotNull DataInput in) throws IOException {
int[] value = new int[DataInputOutputUtil.readINT(in)];
for (int i = 0; i < value.length; i++) {
value[i] = DataInputOutputUtil.readINT(in);
}
return value;
}
@Override
public int getHashCode(int[] value) {
return Arrays.hashCode(value);
}
@Override
public boolean isEqual(int[] val1, int[] val2) {
return Arrays.equals(val1, val2);
}
}
}
@@ -52,7 +52,15 @@ enum Value {
Bot, NotNull, Null, True, False, Top
}
interface Direction {}
interface Direction {
static final int OUT_DIRECTION = 0;
static final int IN_DIRECTION = 1;
static final int INOUT_DIRECTION = 2;
int directionId();
int paramId();
int valueId();
}
final class In implements Direction {
final int paramIndex;
@@ -78,6 +86,21 @@ final class In implements Direction {
public int hashCode() {
return paramIndex;
}
@Override
public int directionId() {
return IN_DIRECTION;
}
@Override
public int paramId() {
return paramIndex;
}
@Override
public int valueId() {
return 0;
}
}
final class InOut implements Direction {
@@ -113,6 +136,21 @@ final class InOut implements Direction {
public String toString() {
return "InOut " + paramIndex + " " + inValue.toString();
}
@Override
public int directionId() {
return INOUT_DIRECTION;
}
@Override
public int paramId() {
return paramIndex;
}
@Override
public int valueId() {
return inValue.ordinal();
}
}
final class Out implements Direction {
@@ -130,6 +168,21 @@ final class Out implements Direction {
public boolean equals(Object obj) {
return obj instanceof Out;
}
@Override
public int directionId() {
return OUT_DIRECTION;
}
@Override
public int paramId() {
return 0;
}
@Override
public int valueId() {
return 0;
}
}
final class Key {
@@ -18,6 +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.TestAnnotation;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -27,10 +30,14 @@ import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.org.objectweb.asm.Type;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Arrays;
/**
* @author lambdamix
@@ -40,14 +47,25 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
private final String myClassesProjectRelativePath = "/classes/" + Test01.class.getPackage().getName().replace('.', '/');
private JavaPsiFacade myJavaPsiFacade;
private InferredAnnotationsManager myInferredAnnotationsManager;
private BytecodeAnalysisConverter myBytecodeAnalysisConverter;
@Override
protected void setUp() throws Exception {
super.setUp();
myJavaPsiFacade = JavaPsiFacade.getInstance(myModule.getProject());
myInferredAnnotationsManager = InferredAnnotationsManager.getInstance(myModule.getProject());
myBytecodeAnalysisConverter = BytecodeAnalysisConverter.getInstance();
setUpDataClasses();
}
@Override
protected void tearDown() throws Exception {
myBytecodeAnalysisConverter.disposeComponent();
super.tearDown();
}
// TODO - test it, possible solution - via external annotation manager??
/*
public void testVelocityJar() {
VirtualFile lib = LocalFileSystem.getInstance().refreshAndFindFileByPath(PathManagerEx.getTestDataPath() + "/../../../lib");
@@ -60,15 +78,22 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
}
*/
public void testDataClasses() throws IOException {
VirtualFile dataClassesDir = setUpDataClasses();
PsiTestUtil.addLibrary(myModule, "dataClasses", dataClassesDir.getPath(), new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY);
//PsiClass psiClass = myJavaPsiFacade.findClass(Test01.class.getName(), GlobalSearchScope.moduleWithLibrariesScope(myModule));
//assertNotNull(psiClass);
public void testInference() throws IOException {
checkAnnotations(Test01.class);
}
public void testCompoundKeys() 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(TestAnnotation.class);
}
private void checkAnnotations(Class<?> javaClass) {
PsiClass psiClass = myJavaPsiFacade.findClass(javaClass.getName(), GlobalSearchScope.moduleWithLibrariesScope(myModule));
assertNotNull(psiClass);
@@ -111,13 +136,59 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
}
}
private VirtualFile setUpDataClasses() throws IOException {
private void checkCompoundIds(Class<?> javaClass) throws IOException {
String javaClassName = javaClass.getCanonicalName();
System.out.println(javaClassName);
PsiClass psiClass = myJavaPsiFacade.findClass(javaClassName, GlobalSearchScope.moduleWithLibrariesScope(myModule));
assertNotNull(psiClass);
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));
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));
boolean noKey = constructor.getAnnotation(ExpectNoPsiKey.class) != null;
PsiMethod[] constructors = psiClass.getConstructors();
PsiMethod psiMethod = constructors[0];
checkCompoundId(method, psiMethod, noKey);
}
}
private void checkCompoundId(Method method, PsiMethod psiMethod, boolean noKey) throws IOException {
Direction direction = new Out();
int[] psiKey = myBytecodeAnalysisConverter.mkCompoundKey(psiMethod, direction);
if (noKey) {
assertNull(psiKey);
return;
}
else {
assertNotNull(psiKey);
}
int[] asmKey = myBytecodeAnalysisConverter.mkCompoundKey(new Key(method, direction));
System.out.println(Arrays.toString(asmKey));
System.out.println(Arrays.toString(psiKey));
System.out.println(myBytecodeAnalysisConverter.showCompoundKey(asmKey));
System.out.println(myBytecodeAnalysisConverter.showCompoundKey(psiKey));
Assert.assertArrayEquals(asmKey, psiKey);
}
private void setUpDataClasses() throws IOException {
File classesDir = new File(Test01.class.getResource(".").getFile());
File destDir = new File(myModule.getProject().getBaseDir().getPath() + myClassesProjectRelativePath);
FileUtil.copyDir(classesDir, destDir);
VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(destDir);
assertNotNull(vFile);
return vFile;
PsiTestUtil.addLibrary(myModule, "dataClasses", vFile.getPath(), new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY);
}
}
@@ -0,0 +1,26 @@
/*
* 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;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author lambdamix
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpectNoPsiKey {
}
@@ -0,0 +1,54 @@
/*
* 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;
}
}
@@ -0,0 +1,54 @@
/*
* 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;
}
}
@@ -0,0 +1,26 @@
/*
* 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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author lambdamix
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}