Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Herrmann2019-08-17 11:46:05 +0000
committerStephan Herrmann2019-08-17 21:38:41 +0000
commit0b88ea1055cc55a836dcd4fe5e59ad3e00164797 (patch)
tree666d5a18053e48285b5f97db3733d49febcbf7a5
parent372c6048cc1b21d8a4ceb948bf5eaecaa40576c9 (diff)
downloadeclipse.jdt.core-I20190817-1800.tar.gz
eclipse.jdt.core-I20190817-1800.tar.xz
eclipse.jdt.core-I20190817-1800.zip
Bug 471009 - [null] extend "Annotate" command, to work on typeI20190817-1800
-rw-r--r--org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests18.java4
-rw-r--r--org.eclipse.jdt.core.tests.model/JCL/jclMin1.8.jarbin13880 -> 13995 bytes
-rw-r--r--org.eclipse.jdt.core.tests.model/JCL/jclMin1.8src.zipbin9805 -> 10189 bytes
-rw-r--r--org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java280
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java8
-rw-r--r--org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java16
-rw-r--r--org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java283
7 files changed, 558 insertions, 33 deletions
diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests18.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests18.java
index 1dd42e9d83..7cd8a8c7b9 100644
--- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests18.java
+++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests18.java
@@ -599,8 +599,8 @@ public class IncrementalTests18 extends BuilderTests {
projectPath,
"Problem : Dead code [" +
" resource : </Project/src/testNullAnnotations/NonNullUtils.java> range : <145,202> category : <90> severity : <1>]\n" +
- "Problem : Null type mismatch (type annotations): required '@NonNull Object @NonNull[]' but this expression has type '@NonNull Object @Nullable[]' [" +
- " resource : </Project/src/testNullAnnotations/Snippet.java> range : <316,323> category : <90> severity : <2>]");
+ "Problem : Null type mismatch: required \'@NonNull Object @NonNull[]\' but the provided value is null [" +
+ " resource : </Project/src/testNullAnnotations/Snippet.java> range : <316,323> category : <90> severity : <2>]");
}
public void testBug481276b() throws Exception {
diff --git a/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8.jar b/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8.jar
index 0e017da1d4..d7e520134e 100644
--- a/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8.jar
+++ b/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8.jar
Binary files differ
diff --git a/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8src.zip b/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8src.zip
index c85ea263d4..aed574c89d 100644
--- a/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8src.zip
+++ b/org.eclipse.jdt.core.tests.model/JCL/jclMin1.8src.zip
Binary files differ
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
index 002f1efaec..420e391b28 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations18Test.java
@@ -53,10 +53,12 @@ import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.tests.util.AbstractCompilerTest;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.core.util.ExternalAnnotationUtil;
@@ -1146,7 +1148,7 @@ public class ExternalAnnotations18Test extends ModifyingResourceTests {
CompilationUnit unit = (CompilationUnit) parser.createAST(null);
libWorkingCopy.discardWorkingCopy();
- // find type binding:
+ // find method binding:
int start = lib1Content.indexOf("U string");
ASTNode name = NodeFinder.perform(unit, start, 0);
assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
@@ -1195,6 +1197,282 @@ public class ExternalAnnotations18Test extends ModifyingResourceTests {
readFully(annotationFile));
}
+ public void testAnnotateMethodTypeParameter1() throws Exception {
+ myCreateJavaProject("TestLibs");
+ String lib1Content =
+ "package libs;\n" +
+ "import java.util.List;\n" +
+ "\n" +
+ "public abstract class Lib1 {\n" +
+ " public abstract <S extends Throwable, T, U extends List<T>, V> U method(S s, T t, V v);\n" +
+ "}\n";
+ addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
+ "/UnannotatedLib/libs/Lib1.java",
+ lib1Content
+ }, null);
+
+ // type check sources:
+ IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
+ ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
+ "package tests;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "import libs.Lib1;\n" +
+ "\n" +
+ "public class Test1 {\n" +
+ " @NonNull Object test0(Lib1 xLib1) {\n" +
+ " return xLib1.method(\n" +
+ " (Error) null, this, xLib1);\n" +
+ " }\n" +
+ "}\n",
+ true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
+ CompilationUnit reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertProblems(reconciled.getProblems(), new String[] {
+ "Pb(981) Unsafe interpretation of method return type as '@NonNull' based on substitution 'U=@NonNull List<Test1>'. Declaring type 'Lib1' doesn't seem to be designed with null type annotations in mind",
+ }, new int[] { 7 });
+
+ // acquire library AST:
+ IType type = this.project.findType("libs.Lib1");
+ ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
+ ASTParser parser = ASTParser.newParser(getJLS8());
+ parser.setSource(libWorkingCopy);
+ parser.setResolveBindings(true);
+ parser.setStatementsRecovery(false);
+ parser.setBindingsRecovery(false);
+ CompilationUnit unit = (CompilationUnit) parser.createAST(null);
+ libWorkingCopy.discardWorkingCopy();
+
+ // find method binding:
+ int start = lib1Content.indexOf("method");
+ ASTNode name = NodeFinder.perform(unit, start, 0);
+ assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
+ ASTNode method = name.getParent();
+ IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
+
+ // find annotation file (not yet existing):
+ IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
+ assertFalse("file should not exist", annotationFile.exists());
+ assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());
+
+ // annotate:
+ String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
+ ExternalAnnotationUtil.annotateMethodTypeParameter("libs/Lib1", annotationFile,
+ "method",
+ originalSignature,
+ "1U::Ljava/util/List<TT;>;", // <- @NonNull U
+ 2, // annotate 3rd type parameter (U)
+ MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+ assertTrue("file should exist", annotationFile.exists());
+
+ // check that the error is resolved now:
+ reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertNoProblems(reconciled.getProblems());
+
+ // add one more annotation:
+ ExternalAnnotationUtil.annotateMethodTypeParameter("libs/Lib1", annotationFile,
+ "method",
+ originalSignature,
+ "1S:Ljava/lang/Throwable;", // <- @NonNull S
+ 0, // annotate 1st type parameter (S)
+ MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+
+ // check that we have a new error now:
+ reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertProblems(reconciled.getProblems(), new String[] {
+ "Pb(910) Null type mismatch: required '@NonNull Error' but the provided value is null",
+ }, new int[] { 8 });
+
+ assertEquals("file content",
+ "class libs/Lib1\n" +
+ "method\n" +
+ " <S:Ljava/lang/Throwable;T:Ljava/lang/Object;U::Ljava/util/List<TT;>;V:Ljava/lang/Object;>(TS;TT;TV;)TU;\n" +
+ " <1S:Ljava/lang/Throwable;T:Ljava/lang/Object;1U::Ljava/util/List<TT;>;V:Ljava/lang/Object;>(TS;TT;TV;)TU;\n",
+ readFully(annotationFile));
+ }
+
+ public void testAnnotateMethodTypeParameter2() throws Exception {
+ myCreateJavaProject("TestLibs");
+ String lib1Content =
+ "package libs;\n" +
+ "import java.util.List;\n" +
+ "\n" +
+ "public abstract class Entry<KK,VV> {\n" +
+ " public static <K, V extends Comparable<? super V>> Comparator<Entry<K,V>> comparingByValue() { return null; }\n" +
+ "}\n";
+ addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
+ "/UnannotatedLib/libs/Comparator.java",
+ "package libs;\n" +
+ "public class Comparator<T> {}\n",
+ "/UnannotatedLib/libs/Entry.java",
+ lib1Content
+ }, null);
+
+ // acquire library AST:
+ IType type = this.project.findType("libs.Entry");
+ ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
+ ASTParser parser = ASTParser.newParser(getJLS8());
+ parser.setSource(libWorkingCopy);
+ parser.setResolveBindings(true);
+ parser.setStatementsRecovery(false);
+ parser.setBindingsRecovery(false);
+ CompilationUnit unit = (CompilationUnit) parser.createAST(null);
+ libWorkingCopy.discardWorkingCopy();
+
+ // find method binding:
+ int start = lib1Content.indexOf("comparingByValue");
+ ASTNode name = NodeFinder.perform(unit, start, 0);
+ assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
+ ASTNode method = name.getParent();
+ IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
+
+ // find annotation file (not yet existing):
+ IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
+ assertFalse("file should not exist", annotationFile.exists());
+ assertEquals("file path", "/TestLibs/annots/libs/Entry.eea", annotationFile.getFullPath().toString());
+
+ // annotate:
+ String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
+ // preview:
+ String[] annotatedSign = ExternalAnnotationUtil.annotateTypeParameter(
+ originalSignature,
+ "1K:Ljava/lang/Object;", // <- @NonNull K
+ 0,
+ MergeStrategy.OVERWRITE_ANNOTATIONS);
+ assertEquals("dry-run result", "[<, " +
+ "K:Ljava/lang/Object;, " +
+ "1K:Ljava/lang/Object;, " + // <- K
+ "V::Ljava/lang/Comparable<-TV;>;>()Llibs/Comparator<Llibs/Entry<TK;TV;>;>;]",
+ Arrays.toString(annotatedSign));
+ // perform:
+ ExternalAnnotationUtil.annotateMethodTypeParameter("libs/Entry", annotationFile,
+ "comparingByValue",
+ originalSignature,
+ "1K:Ljava/lang/Object;", // <- @NonNull K
+ 0, // annotate 1st type parameter (K)
+ MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+ assertTrue("file should exist", annotationFile.exists());
+
+ // type check sources:
+ IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
+ ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
+ "package tests;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "import libs.*;\n" +
+ "\n" +
+ "public class Test1 {\n" +
+ " Object test0() {\n" +
+ " Comparator<Entry<@Nullable String,String>> c = Entry.comparingByValue();\n" +
+ " return c;\n" +
+ " }\n" +
+ "}\n",
+ true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
+ CompilationUnit reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertProblems(reconciled.getProblems(), new String[] {
+ "Pb(953) Null type mismatch (type annotations): required 'Comparator<Entry<@Nullable String,String>>' but this expression has type 'Comparator<Entry<@NonNull String,String>>'",
+ }, new int[] { 7 });
+
+ // un-annotate:
+ annotatedSign = ExternalAnnotationUtil.annotateTypeParameter(
+ originalSignature,
+ "@K:Ljava/lang/Object;", // <- <del>1</del> K
+ 0, MergeStrategy.OVERWRITE_ANNOTATIONS);
+ assertEquals("dry-run result", "[<, " +
+ "K:Ljava/lang/Object;, " +
+ "K:Ljava/lang/Object;, " + // <- K
+ "V::Ljava/lang/Comparable<-TV;>;>()Llibs/Comparator<Llibs/Entry<TK;TV;>;>;]",
+ Arrays.toString(annotatedSign));
+ }
+
+ public void testAnnotateClassTypeParameter1() throws Exception {
+ myCreateJavaProject("TestLibs");
+ String lib1Content =
+ "package libs;\n" +
+ "import java.util.List;\n" +
+ "\n" +
+ "public abstract class Lib1 <S extends Throwable, T, U extends List<T>, V> {\n" +
+ " public abstract U method(S s, T t, V v);\n" +
+ "}\n";
+ addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
+ "/UnannotatedLib/libs/Lib1.java",
+ lib1Content
+ }, null);
+
+ // type check sources:
+ IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
+ ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
+ "package tests;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "import libs.Lib1;\n" +
+ "import java.util.List;\n" +
+ "\n" +
+ "public class Test1 {\n" +
+ " @NonNull List<Test1> test0(Lib1<Error,Test1,@NonNull List<Test1>,String> xLib1) {\n" +
+ " return xLib1.method(\n" +
+ " (Error) null, this, \"1\");\n" +
+ " }\n" +
+ "}\n",
+ true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
+ CompilationUnit reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertProblems(reconciled.getProblems(), new String[] {
+ "Pb(980) Unsafe interpretation of method return type as '@NonNull' based on the receiver type 'Lib1<Error,Test1,@NonNull List<Test1>,String>'. Type 'Lib1<S,T,U,V>' doesn't seem to be designed with null type annotations in mind",
+ }, new int[] { 8 });
+
+ // acquire library AST:
+ IType type = this.project.findType("libs.Lib1");
+ ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
+ ASTParser parser = ASTParser.newParser(getJLS8());
+ parser.setSource(libWorkingCopy);
+ parser.setResolveBindings(true);
+ parser.setStatementsRecovery(false);
+ parser.setBindingsRecovery(false);
+ CompilationUnit unit = (CompilationUnit) parser.createAST(null);
+ libWorkingCopy.discardWorkingCopy();
+
+ // find type binding:
+ int start = lib1Content.indexOf("Lib1");
+ ASTNode name = NodeFinder.perform(unit, start, 0);
+ assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
+ ASTNode typeDecl = name.getParent();
+ ITypeBinding typeBinding = ((TypeDeclaration)typeDecl).resolveBinding();
+
+ // find annotation file (not yet existing):
+ IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, typeBinding, null);
+ assertFalse("file should not exist", annotationFile.exists());
+ assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());
+
+ // annotate:
+ String originalSignature = ExternalAnnotationUtil.extractGenericTypeParametersSignature(typeBinding);
+ ExternalAnnotationUtil.annotateTypeTypeParameter("libs/Lib1", annotationFile,
+ originalSignature,
+ "1U::Ljava/util/List<TT;>;", // <- @NonNull U
+ 2, // annotate 3rd type parameter (U)
+ MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+ assertTrue("file should exist", annotationFile.exists());
+
+ // check that the error is resolved now:
+ reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertNoProblems(reconciled.getProblems());
+
+ // add one more annotation:
+ ExternalAnnotationUtil.annotateTypeTypeParameter("libs/Lib1", annotationFile,
+ originalSignature,
+ "1S:Ljava/lang/Throwable;", // <- @NonNull S
+ 0, // annotate 1st type parameter (S)
+ MergeStrategy.OVERWRITE_ANNOTATIONS, null);
+
+ // check that we have a new error now:
+ reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
+ assertProblems(reconciled.getProblems(), new String[] {
+ "Pb(964) Null constraint mismatch: The type 'Error' is not a valid substitute for the type parameter '@NonNull S extends Throwable'",
+ "Pb(910) Null type mismatch: required '@NonNull Error' but the provided value is null",
+ }, new int[] { 7, 9 });
+
+ assertEquals("file content",
+ "class libs/Lib1\n" +
+ " <S:Ljava/lang/Throwable;T:Ljava/lang/Object;U::Ljava/util/List<TT;>;V:Ljava/lang/Object;>\n" +
+ " <1S:Ljava/lang/Throwable;T:Ljava/lang/Object;1U::Ljava/util/List<TT;>;V:Ljava/lang/Object;>\n",
+ readFully(annotationFile));
+ }
+
// ===== white box tests for ExternalAnnotationUtil =====
public void testBug470666a() throws CoreException, IOException {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
index abda6fd7fd..bf6658821b 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java
@@ -302,8 +302,11 @@ public class NullAnnotationMatching {
}
}
severity = severity.max(dimSeverity);
- if (severity == Severity.MISMATCH)
+ if (severity == Severity.MISMATCH) {
+ if (nullStatus == FlowInfo.NULL)
+ return new NullAnnotationMatching(severity, nullStatus, null);
return NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH;
+ }
}
if (severity == Severity.OK)
nullStatus = -1;
@@ -568,6 +571,9 @@ public class NullAnnotationMatching {
} else if (requiredBits == TagBits.AnnotationNonNull) {
switch (mode) {
case COMPATIBLE:
+ if (nullStatus == FlowInfo.NULL)
+ return Severity.MISMATCH; // NOK by flow analysis
+ //$FALL-THROUGH$
case BOUND_SUPER_CHECK:
if (nullStatus == FlowInfo.NON_NULL)
return Severity.OK; // OK by flow analysis
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java
index 9425c40ea9..4e4fa4b504 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SignatureWrapper.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -145,6 +145,20 @@ public class SignatureWrapper {
}
return i;
}
+ public int skipTypeParameter() {
+ // [Annot] Identifier ClassBound {InterfaceBound}
+ this.start = CharOperation.indexOf(':', this.signature, this.start);
+ while (charAtStart() == ':') {
+ this.start++;
+ if (charAtStart() != ':') // ClassBound may be empty
+ this.start = skipAngleContents(computeEnd()) + 1;
+ }
+ return this.start;
+ }
+ public char[] wordUntil(char c) {
+ this.end = CharOperation.indexOf(c, this.signature, this.start);
+ return CharOperation.subarray(this.signature, this.start, this.start = this.end); // skip word
+ }
public char[] nextWord() {
this.end = CharOperation.indexOf(';', this.signature, this.start);
if (this.bracket <= this.start) // already know it if its > start
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java
index 301b4e5bd5..67dba5de30 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/util/ExternalAnnotationUtil.java
@@ -39,6 +39,7 @@ import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
+import org.eclipse.jdt.internal.compiler.codegen.ConstantPool;
import org.eclipse.jdt.internal.compiler.lookup.SignatureWrapper;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.eclipse.jdt.internal.core.util.KeyToSignature;
@@ -76,6 +77,7 @@ public final class ExternalAnnotationUtil {
private static final int POSITION_RETURN_TYPE = -1;
private static final int POSITION_FULL_SIGNATURE = -2;
+ private static final int POSITION_TYPE_PARAMETER = -3;
/**
* Answer the give method's signature in class file format.
@@ -101,6 +103,36 @@ public final class ExternalAnnotationUtil {
}
/**
+ * Answer the signature of all type parameters of this type in class file format.
+ * @param type binding representing a type, required to have type parameters
+ * @return a signature in class file format
+ * @since 3.19
+ */
+ public static String extractGenericTypeParametersSignature(ITypeBinding type) {
+ StringBuilder signature = new StringBuilder().append('<');
+ for (ITypeBinding typeParameter : type.getTypeParameters()) {
+ signature.append(typeParameter.getName());
+ // superclass (if relevant, else empty ':'):
+ signature.append(':');
+ ITypeBinding superclass = typeParameter.getSuperclass();
+ if (superclass != null) {
+ String superclassSignature = extractGenericTypeSignature(superclass);
+ boolean superIsRelevant = typeParameter.getInterfaces().length == 0;
+ superIsRelevant |= !superclassSignature.equals(new String(ConstantPool.JavaLangObjectSignature));
+ if (superIsRelevant) {
+ signature.append(superclassSignature);
+ }
+ }
+ // superinterfaces:
+ for (ITypeBinding superInterface : typeParameter.getInterfaces()) {
+ signature.append(':').append(extractGenericTypeSignature(superInterface));
+ }
+ }
+ signature.append('>');
+ return signature.toString();
+ }
+
+ /**
* Insert an encoded annotation into the given methodSignature affecting its return type.
* <p>
* This method is suitable for declaration annotations.
@@ -217,6 +249,59 @@ public final class ExternalAnnotationUtil {
}
/**
+ * Update the given external annotation file with details regarding annotations of a type parameter of the type itself.
+ * If the type already has external annotations, old and new annotations will be merged,
+ * with priorities controlled by the parameter 'mergeStrategy'.
+ * <p>
+ * This method is suitable only for type use annotations.
+ * </p>
+ * @param typeName binary name (slash separated) of the type being annotated
+ * @param file a file assumed to be in .eea format, will be created if it doesn't exist.
+ * @param originalSignature unannotated signature of all type parameters of the type
+ * @param annotatedTypeParameter signature of the new type parameter whose annotations should be superimposed on the method
+ * @param rank rank of the type parameter to be annotated
+ * @param mergeStrategy controls how old and new signatures should be merged
+ * @param monitor progress monitor to be passed through into file operations, or null if no reporting is desired
+ * @throws CoreException if access to the file fails
+ * @throws IOException if reading file content fails
+ * @throws IllegalArgumentException if the annotatedTypeParameter does not structurally match to originalSignature
+ * @since 3.19
+ */
+ public static void annotateTypeTypeParameter(String typeName, IFile file, String originalSignature, String annotatedTypeParameter,
+ int rank, MergeStrategy mergeStrategy, IProgressMonitor monitor)
+ throws CoreException, IOException, IllegalArgumentException
+ {
+ annotateMember(typeName, file, null, originalSignature, annotatedTypeParameter, POSITION_TYPE_PARAMETER-rank, mergeStrategy, monitor);
+ }
+
+ /**
+ * Update the given external annotation file with details regarding annotations of a type parameter of a given method.
+ * If the specified method already has external annotations, old and new annotations will be merged,
+ * with priorities controlled by the parameter 'mergeStrategy'.
+ * <p>
+ * This method is suitable only for type use annotations.
+ * </p>
+ * @param typeName binary name (slash separated) of the type being annotated
+ * @param file a file assumed to be in .eea format, will be created if it doesn't exist.
+ * @param selector selector of the method
+ * @param originalSignature unannotated signature of the member, used for identification
+ * @param annotatedTypeParameter signature of the new type parameter whose annotations should be superimposed on the method
+ * @param rank rank of the type parameter to be annotated
+ * @param mergeStrategy controls how old and new signatures should be merged
+ * @param monitor progress monitor to be passed through into file operations, or null if no reporting is desired
+ * @throws CoreException if access to the file fails
+ * @throws IOException if reading file content fails
+ * @throws IllegalArgumentException if the annotatedTypeParameter does not structurally match to originalSignature
+ * @since 3.19
+ */
+ public static void annotateMethodTypeParameter(String typeName, IFile file, String selector, String originalSignature, String annotatedTypeParameter,
+ int rank, MergeStrategy mergeStrategy, IProgressMonitor monitor)
+ throws CoreException, IOException, IllegalArgumentException
+ {
+ annotateMember(typeName, file, selector, originalSignature, annotatedTypeParameter, POSITION_TYPE_PARAMETER-rank, mergeStrategy, monitor);
+ }
+
+ /**
* Update the given external annotation file with details regarding annotations of the return type of a given method.
* If the specified method already has external annotations, old and new annotations will be merged,
* with priorities controlled by the parameter 'mergeStrategy'.
@@ -281,7 +366,9 @@ public final class ExternalAnnotationUtil {
newContent.append(ExternalAnnotationProvider.CLASS_PREFIX);
newContent.append(typeName).append('\n');
// new entry:
- newContent.append(selector).append('\n');
+ if (selector != null) { // otherwise we are annotating a class type parameter
+ newContent.append(selector).append('\n');
+ }
newContent.append(' ').append(originalSignature).append('\n');
newContent.append(' ').append(annotatedSignature).append('\n');
@@ -290,9 +377,22 @@ public final class ExternalAnnotationUtil {
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getContents()));
StringBuffer newContent = new StringBuffer();
try {
+ // type references get the previous signature from the existing type binding:
+ String previousSignature = originalSignature;
newContent.append(reader.readLine()).append('\n'); // skip class name
String line;
while ((line = reader.readLine()) != null) {
+ if (selector == null) { // annotating a type parameter?
+ if (line.trim().startsWith("<")) { //$NON-NLS-1$
+ line = reader.readLine();
+ assert line.trim().startsWith("<"); // reading old annotated type parameters //$NON-NLS-1$
+ // bindings of type parameters (being declarations) don't include annotations in their signature,
+ // so we need to preserve the previous signature from the .eea:
+ previousSignature = line.trim();
+ line = reader.readLine();
+ }
+ break;
+ }
if (line.isEmpty()) {
newContent.append('\n');
continue;
@@ -353,9 +453,10 @@ public final class ExternalAnnotationUtil {
}
}
// add new entry:
- newContent.append(selector).append('\n');
+ if (selector != null)
+ newContent.append(selector).append('\n');
newContent.append(' ').append(originalSignature).append('\n');
- annotatedSignature = updateSignature(originalSignature, annotatedSignature, updatePosition, mergeStrategy);
+ annotatedSignature = updateSignature(previousSignature, annotatedSignature, updatePosition, mergeStrategy);
writeFile(file, newContent, annotatedSignature, line, reader, monitor);
} finally {
reader.close();
@@ -367,28 +468,48 @@ public final class ExternalAnnotationUtil {
StringBuffer buf = new StringBuffer();
String signatureToReplace;
String postfix = null;
- switch (updatePosition) {
- case POSITION_FULL_SIGNATURE:
- signatureToReplace = originalSignature;
- break;
- case POSITION_RETURN_TYPE:
- assert originalSignature.charAt(0) == '(' || originalSignature.charAt(0) == '<': "signature must start with '(' or '<'"; //$NON-NLS-1$
- int close = originalSignature.indexOf(')');
- buf.append(originalSignature, 0, close+1);
- signatureToReplace = originalSignature.substring(close+1);
- break;
- default: // parameter
- SignatureWrapper wrapper = new SignatureWrapper(originalSignature.toCharArray(), true, true); // may already contain annotations
- wrapper.start = CharOperation.indexOf('(', wrapper.signature) + 1; // possibly skipping type parameters
- for (int i = 0; i < updatePosition; i++)
- wrapper.start = wrapper.skipAngleContents(wrapper.computeEnd()) + 1;
- int start = wrapper.start;
- int end = wrapper.skipAngleContents(wrapper.computeEnd());
- buf.append(originalSignature, 0, start);
- signatureToReplace = originalSignature.substring(start, end+1);
- postfix = originalSignature.substring(end+1, originalSignature.length());
+ if (updatePosition <= POSITION_TYPE_PARAMETER) {
+ // '<' [Annot] Identifier ClassBound {InterfaceBound} ... '>'
+ assert originalSignature.charAt(0) == '<': "generic signature must start with '<'"; //$NON-NLS-1$
+ SignatureWrapper wrapper = new SignatureWrapper(originalSignature.toCharArray(), true, true); // may already contain annotations
+ wrapper.start = 1; // skip '<'
+ // skip preceding type parameters:
+ for (int i = 0; i < (-updatePosition+POSITION_TYPE_PARAMETER); i++) {
+ wrapper.skipTypeParameter();
+ }
+ int start = wrapper.start;
+ // copy entire prefix:
+ buf.append(originalSignature, 0, start);
+ // process selected type parameter:
+ int end = wrapper.skipTypeParameter();
+ signatureToReplace = originalSignature.substring(start, end);
+ updateTypeParameter(buf, signatureToReplace.toCharArray(), annotatedSignature.toCharArray(), mergeStrategy);
+ // extract postfix:
+ postfix = originalSignature.substring(end, originalSignature.length());
+ } else {
+ switch (updatePosition) {
+ case POSITION_FULL_SIGNATURE:
+ signatureToReplace = originalSignature;
+ break;
+ case POSITION_RETURN_TYPE:
+ assert originalSignature.charAt(0) == '(' || originalSignature.charAt(0) == '<': "signature must start with '(' or '<'"; //$NON-NLS-1$
+ int close = originalSignature.indexOf(')');
+ buf.append(originalSignature, 0, close+1);
+ signatureToReplace = originalSignature.substring(close+1);
+ break;
+ default: // parameter
+ SignatureWrapper wrapper = new SignatureWrapper(originalSignature.toCharArray(), true, true); // may already contain annotations
+ wrapper.start = CharOperation.indexOf('(', wrapper.signature) + 1; // possibly skipping type parameters
+ for (int i = 0; i < updatePosition; i++)
+ wrapper.start = wrapper.skipAngleContents(wrapper.computeEnd()) + 1;
+ int start = wrapper.start;
+ int end = wrapper.skipAngleContents(wrapper.computeEnd());
+ buf.append(originalSignature, 0, start);
+ signatureToReplace = originalSignature.substring(start, end+1);
+ postfix = originalSignature.substring(end+1, originalSignature.length());
+ }
+ updateType(buf, signatureToReplace.toCharArray(), annotatedSignature.toCharArray(), mergeStrategy);
}
- updateType(buf, signatureToReplace.toCharArray(), annotatedSignature.toCharArray(), mergeStrategy);
if (postfix != null)
buf.append(postfix);
return buf.toString();
@@ -461,6 +582,62 @@ public final class ExternalAnnotationUtil {
return false;
}
/**
+ * similar to updateType() but for type parameters, syntax:
+ * [Annot] Identifier ClassBound {InterfaceBound}
+ */
+ private static boolean updateTypeParameter(StringBuffer buf, char[] oldType, char[] newType, MergeStrategy mergeStrategy) {
+ if (mergeStrategy == MergeStrategy.REPLACE_SIGNATURE) {
+ buf.append(newType);
+ return false;
+ }
+ try {
+ SignatureWrapper oWrap = new SignatureWrapper(oldType, true, true); // may already contain annotations
+ SignatureWrapper nWrap = new SignatureWrapper(newType, true, true); // may already contain annotations
+ // [Annot]
+ mergeAnnotation(buf, oWrap, nWrap, mergeStrategy);
+ // Identifier:
+ char[] oName = oWrap.wordUntil(':');
+ char[] nName = nWrap.wordUntil(':');
+ if (!CharOperation.equals(oName, nName)) {
+ StringBuilder msg = new StringBuilder("Structural mismatch between type parameters ").append(oName).append(" and ").append(nName); //$NON-NLS-1$ //$NON-NLS-2$
+ throw new IllegalArgumentException(msg.toString());
+ }
+ buf.append(oName);
+ // one or more bounds each starting with ':'
+ while (match(buf, oWrap, nWrap, ':', false)) {
+ int oStart = oWrap.start;
+ int nStart = nWrap.start;
+ nWrap.skipAngleContents(nWrap.computeEnd());
+ char[] nType = nWrap.getFrom(nStart);
+ char[] oType;
+ if (oWrap.charAtStart() == ':') {
+ // old bound is empty!
+ if (CharOperation.equals(nType, new char[]{':'})) {
+ nWrap.start--; // unget second ':'
+ continue; // both bounds empty
+ }
+ if (CharOperation.equals(nType, ConstantPool.JavaLangObjectSignature)) {
+ // new: j.l.Object => skip
+ continue;
+ }
+ // new is not exactly Ljava/lang/Object; (perhaps already annotated?) => replace old with j.l.Object for decoration:
+ oType = ConstantPool.JavaLangObjectSignature;
+ } else {
+ oWrap.skipAngleContents(oWrap.computeEnd());
+ oType = oWrap.getFrom(oStart);
+ }
+ if (updateType(buf, oType, nType, mergeStrategy))
+ mergeAnnotation(buf, oWrap, nWrap, mergeStrategy);
+ if (oWrap.atEnd() || nWrap.atEnd())
+ return false;
+ }
+ } catch (ArrayIndexOutOfBoundsException aioobe) { // from several locations inside match() or mergeAnnotation().
+ StringBuilder msg = new StringBuilder("Structural mismatch between ").append(oldType).append(" and ").append(newType); //$NON-NLS-1$ //$NON-NLS-2$
+ throw new IllegalArgumentException(msg.toString(), aioobe);
+ }
+ return false;
+ }
+ /**
* Does the current char at both given signatures match the 'expected' char?
* If yes, print it into 'buf' and answer true.
* If no, if 'force' raise an exception, else quietly answer false without updating 'buf'.
@@ -572,7 +749,7 @@ public final class ExternalAnnotationUtil {
* Retrieve the annotated signature of a specified member as found in the given external annotation file, if any.
* @param typeName fully qualified slash-separated name of the type for which the file defines external annotations
* @param file a file assumed to be in .eea format, must not be null, but may not exist
- * @param selector name of the member whose annotation we are looking for
+ * @param selector name of the member whose annotation we are looking for, or null when annotating the type's type parameters
* @param originalSignature the unannotated signature by which the member is identified
* @return the annotated signature as found in the file, or null.
*/
@@ -583,9 +760,17 @@ public final class ExternalAnnotationUtil {
while (true) {
String line = reader.readLine();
// selector:
- if (selector.equals(line)) {
+ if (selector != null) {
+ if (selector.equals(line)) {
+ // original signature:
+ line = reader.readLine();
+ if (originalSignature.equals(ExternalAnnotationProvider.extractSignature(line))) {
+ // annotated signature:
+ return ExternalAnnotationProvider.extractSignature(reader.readLine());
+ }
+ }
+ } else if (line != null && line.trim().startsWith("<")) { //$NON-NLS-1$
// original signature:
- line = reader.readLine();
if (originalSignature.equals(ExternalAnnotationProvider.extractSignature(line))) {
// annotated signature:
return ExternalAnnotationProvider.extractSignature(reader.readLine());
@@ -664,7 +849,7 @@ public final class ExternalAnnotationUtil {
*
* @param originalSignature the original full signature, may be annotated already
* @param annotatedType a type signature with additional annotations (incl. {@link #NO_ANNOTATION}).
- * @param paramIdx the index of a parameter to annotated
+ * @param paramIdx the index of a parameter to annotate
* @param mergeStrategy controls how old and new signatures should be merged
* @return an array of length four: <ul>
* <li>prefix up-to the changed type</li>
@@ -691,4 +876,46 @@ public final class ExternalAnnotationUtil {
result[3] = originalSignature.substring(end+1, originalSignature.length());
return result;
}
+
+ /**
+ * Apply the specified changes on a type parameter within the given signature.
+ * This method can be used as a dry run without modifying an annotation file.
+ *
+ * @param originalSignature the original full signature, may be annotated already
+ * @param annotatedType a type signature with additional annotations (incl. {@link #NO_ANNOTATION}).
+ * @param rank the index of a type parameter to annotate
+ * @param mergeStrategy controls how old and new signatures should be merged
+ * @return an array of length four: <ul>
+ * <li>prefix up-to the changed type</li>
+ * <li>original type</li>
+ * <li>changed type</li>
+ * <li>postfix after the changed type</li>
+ * </ul>
+ * @since 3.19
+ */
+ public static String[] annotateTypeParameter(String originalSignature, String annotatedType, int rank, MergeStrategy mergeStrategy)
+ {
+ String[] result = new String[4]; // prefix, orig, replacement, postfix
+ StringBuffer buf = new StringBuffer();
+ SignatureWrapper wrapper = new SignatureWrapper(originalSignature.toCharArray(), true, true); // may already contain annotations
+ wrapper.start = 1; // skip '<'
+ // prefix:
+ for (int i = 0; i < rank; i++) {
+ wrapper.skipTypeParameter();
+ }
+ int start = wrapper.start;
+ result[0] = originalSignature.substring(0, start);
+ // orig:
+ int end = wrapper.skipTypeParameter();
+ result[1] = originalSignature.substring(start, end);
+ // replacement:
+ SignatureWrapper oWrap = new SignatureWrapper(result[1].toCharArray());
+ SignatureWrapper nWrap = new SignatureWrapper(annotatedType.toCharArray());
+ mergeAnnotation(buf, oWrap, nWrap, mergeStrategy);
+ updateTypeParameter(buf, oWrap.tail(), nWrap.tail(), mergeStrategy);
+ result[2] = buf.toString();
+ // postfix:
+ result[3] = originalSignature.substring(end, originalSignature.length());
+ return result;
+ }
}

Back to the top