Bug 458201 - Offer new command "Annotate" on ClassFileEditor
Bug 458200 - [null] "Annotate" proposals for adding external null
annotations to library classes
Change-Id: Ibf629c52530df7c06f3d3ccaaf2abb98149c03ac
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AbstractAnnotateAssistTests.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AbstractAnnotateAssistTests.java
new file mode 100644
index 0000000..7fa0570
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AbstractAnnotateAssistTests.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2015 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.quickfix;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jdt.testplugin.JavaProjectHelper;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Path;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+
+import org.eclipse.jdt.internal.core.ClasspathEntry;
+import org.eclipse.jdt.internal.corext.util.Strings;
+
+import org.eclipse.jdt.ui.tests.core.ProjectTestSetup;
+
+import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
+import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer;
+import org.eclipse.jdt.internal.ui.text.correction.ExternalNullAnnotationQuickAssistProcessor;
+import org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionAssistant;
+
+import static org.eclipse.jdt.core.IClasspathAttribute.EXTERNAL_ANNOTATION_PATH;
+
+public abstract class AbstractAnnotateAssistTests extends QuickFixTest {
+
+ protected IJavaProject fJProject1;
+
+ public AbstractAnnotateAssistTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ JavaProjectHelper.clear(fJProject1, ProjectTestSetup.getDefaultClasspath());
+ super.tearDown();
+ }
+
+ protected void ensureExists(IContainer parent) throws CoreException {
+ if (parent.exists()) return;
+ if (parent.getParent() != null)
+ ensureExists(parent.getParent());
+ if (parent instanceof IFolder)
+ ((IFolder)parent).create(true, true, null);
+ }
+
+ public List<ICompletionProposal> collectAnnotateProposals(JavaEditor javaEditor, int offset) {
+ JavaSourceViewer viewer= (JavaSourceViewer)javaEditor.getViewer();
+ viewer.setSelection(new TextSelection(offset, 0));
+
+ JavaCorrectionAssistant correctionAssist= new JavaCorrectionAssistant(javaEditor);
+ IQuickAssistProcessor assistProcessor= new ExternalNullAnnotationQuickAssistProcessor(correctionAssist);
+ ICompletionProposal[] proposals= assistProcessor.computeQuickAssistProposals(viewer.getQuickAssistInvocationContext());
+
+ List<ICompletionProposal> list= Arrays.asList(proposals);
+ return list;
+ }
+
+ // === from jdt.core.tests.model: ===
+
+ protected void addLibrary(IJavaProject javaProject, String jarName, String sourceZipName, String[] pathAndContents,
+ String annotationpath, String compliance) throws CoreException, IOException
+ {
+ IProject project= createLibrary(javaProject, jarName, sourceZipName, pathAndContents, compliance);
+
+ String projectPath= '/' + project.getName() + '/';
+ IClasspathEntry entry= JavaCore.newLibraryEntry(
+ new Path(projectPath + jarName),
+ sourceZipName == null ? null : new Path(projectPath + sourceZipName),
+ null,
+ ClasspathEntry.getAccessRules(null, null),
+ new IClasspathAttribute[] { JavaCore.newClasspathAttribute(EXTERNAL_ANNOTATION_PATH, annotationpath)},
+ true);
+ IClasspathEntry[] entries= javaProject.getRawClasspath();
+ int length= entries.length;
+ System.arraycopy(entries, 0, entries= new IClasspathEntry[length + 1], 0, length);
+ entries[length]= entry;
+ javaProject.setRawClasspath(entries, null);
+ }
+
+ protected IProject createLibrary(IJavaProject javaProject, String jarName, String sourceZipName, String[] pathAndContents,
+ String compliance) throws IOException, CoreException
+ {
+ IProject project= javaProject.getProject();
+ String projectLocation= project.getLocation().toOSString();
+ String jarPath= projectLocation + File.separator + jarName;
+ String[] claspath= new String[] { javaProject.getResolvedClasspath(true)[0].getPath().toOSString() };
+ JarUtil.createJar(pathAndContents, null, jarPath, claspath, compliance, null);
+ if (pathAndContents != null && pathAndContents.length != 0) {
+ String sourceZipPath= projectLocation + File.separator + sourceZipName;
+ JarUtil.createSourceZip(pathAndContents, sourceZipPath);
+ }
+ project.refreshLocal(IResource.DEPTH_INFINITE, null);
+ return project;
+ }
+
+ // === from PropertiesFileQuickAssistTest: ===
+
+ protected static void checkContentOfFile(String message, IFile file, String content) throws Exception {
+ InputStream in= file.getContents();
+ try {
+ assertEqualLines(message, content, copyToString(in));
+ } finally {
+ in.close();
+ }
+ }
+
+ protected static String copyToString(InputStream in) throws Exception {
+ ByteArrayOutputStream out= new ByteArrayOutputStream();
+ int read= in.read();
+ while (read != -1) {
+ out.write(read);
+ read= in.read();
+ }
+ out.close();
+ return out.toString();
+ }
+
+ protected static void assertEqualLines(String message, String expected, String actual) {
+ String[] expectedLines= Strings.convertIntoLines(expected);
+ String[] actualLines= Strings.convertIntoLines(actual);
+
+ String expected2= (expectedLines == null ? null : Strings.concatenate(expectedLines, "\n"));
+ String actual2= (actualLines == null ? null : Strings.concatenate(actualLines, "\n"));
+ assertEquals(message, expected2, actual2);
+ }
+}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AnnotateAssistTest15.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AnnotateAssistTest15.java
new file mode 100644
index 0000000..3aa6f99
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AnnotateAssistTest15.java
@@ -0,0 +1,399 @@
+/*******************************************************************************
+ * Copyright (c) 2015 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.quickfix;
+
+import java.io.ByteArrayInputStream;
+import java.util.List;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.jdt.testplugin.JavaProjectHelper;
+
+import org.eclipse.core.runtime.Path;
+
+import org.eclipse.core.resources.IFile;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.contentassist.ContentAssistEvent;
+import org.eclipse.jface.text.contentassist.ICompletionListener;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.source.SourceViewer;
+
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jdt.ui.tests.core.ProjectTestSetup;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
+import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer;
+
+public class AnnotateAssistTest15 extends AbstractAnnotateAssistTests {
+
+ protected static final String ANNOTATION_PATH= "annots";
+
+ protected static final Class<?> THIS= AnnotateAssistTest15.class;
+
+ public static Test suite() {
+ return setUpTest(new TestSuite(THIS));
+ }
+
+ public static Test setUpTest(Test test) {
+ return new ProjectTestSetup(test);
+ }
+
+ public AnnotateAssistTest15(String name) {
+ super(name);
+ }
+
+ protected void setUp() throws Exception {
+ fJProject1= ProjectTestSetup.getProject();
+ fJProject1.getProject().getFolder(ANNOTATION_PATH).create(true, true, null);
+ fJProject1.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ }
+
+ // === Tests ===
+
+ /**
+ * Assert that the "Annotate" command can be invoked on a ClassFileEditor
+ * @throws Exception
+ */
+ public void testAnnotateReturn() throws Exception {
+
+ String MY_MAP_PATH= "pack/age/MyMap";
+ String[] pathAndContents= new String[] {
+ MY_MAP_PATH+".java",
+ "package pack.age;\n" +
+ "public interface MyMap<K,V> {\n" +
+ " public V get(K key);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_5);
+ IType type= fJProject1.findType(MY_MAP_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ SourceViewer viewer= (SourceViewer)javaEditor.getViewer();
+
+ // invoke the full command and asynchronously collect the result:
+ final ICompletionProposal[] proposalBox= new ICompletionProposal[1];
+ viewer.getQuickAssistAssistant().addCompletionListener(new ICompletionListener() {
+ public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
+ proposalBox[0]= proposal;
+ }
+ public void assistSessionStarted(ContentAssistEvent event) { /* nop */ }
+ public void assistSessionEnded(ContentAssistEvent event) { /* nop */ }
+ });
+
+ int offset= pathAndContents[1].indexOf("V get");
+ viewer.setSelection(new TextSelection(offset, 0));
+ viewer.doOperation(JavaSourceViewer.ANNOTATE_CLASS_FILE);
+
+ int count = 10;
+ while (proposalBox[0] == null && count-- > 0)
+ Thread.sleep(200);
+ ICompletionProposal proposal= proposalBox[0];
+ assertNotNull("should have a proposal", proposal);
+
+ viewer.getQuickAssistAssistant().uninstall();
+ JavaProjectHelper.emptyDisplayLoop();
+
+ assertEquals("expect proposal", "Annotate as '@NonNull V'", proposal.getDisplayString());
+ String expectedInfo=
+ "<dl><dt>get</dt>" +
+ "<dd>(TK;)TV;</dd>" +
+ "<dd>(TK;)T<b>1</b>V;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append("pack/age/MyMap.eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/MyMap\n" +
+ "get\n" +
+ " (TK;)TV;\n" +
+ " (TK;)T1V;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on a simple return type (type variable).
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ public void testAnnotateReturn2() throws Exception {
+
+ String MY_MAP_PATH= "pack/age/MyMap";
+ String[] pathAndContents= new String[] {
+ MY_MAP_PATH+".java",
+ "package pack.age;\n" +
+ "public interface MyMap<K,V> {\n" +
+ " public V get(K key);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_5);
+ IType type= fJProject1.findType(MY_MAP_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("V get");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as '@NonNull V'", list);
+ String expectedInfo=
+ "<dl><dt>get</dt>" +
+ "<dd>(TK;)TV;</dd>" +
+ "<dd>(TK;)T<b>1</b>V;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as '@Nullable V'", list);
+ expectedInfo=
+ "<dl><dt>get</dt>" +
+ "<dd>(TK;)TV;</dd>" +
+ "<dd>(TK;)T<b>0</b>V;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(MY_MAP_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/MyMap\n" +
+ "get\n" +
+ " (TK;)TV;\n" +
+ " (TK;)T0V;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "Remove") if annotation file already says "@Nullable".
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ public void testAnnotateRemove() throws Exception {
+
+ String MY_MAP_PATH= "pack/age/MyMap";
+
+ String[] pathAndContents= new String[] {
+ MY_MAP_PATH+".java",
+ "package pack.age;\n" +
+ "public interface MyMap<K,V> {\n" +
+ " public V get(K key);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_5);
+ IType type= fJProject1.findType(MY_MAP_PATH.replace('/', '.'));
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(MY_MAP_PATH+".eea"));
+ String initialContent=
+ "class pack/age/MyMap\n" +
+ "get\n" +
+ " (TK;)TV;\n" +
+ " (TK;)T0V;\n";
+ ensureExists(annotationFile.getParent());
+ annotationFile.create(new ByteArrayInputStream(initialContent.getBytes("UTF-8")), 0, null);
+
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("V get");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as '@NonNull V'", list);
+ String expectedInfo=
+ "<dl><dt>get</dt>" +
+ "<dd>(TK;)TV;</dd>" +
+ "<dd>(TK;)T<b>1</b>V;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Remove nullness annotation from type 'V'", list);
+ expectedInfo=
+ "<dl><dt>get</dt>" +
+ "<dd>(TK;)TV;</dd>" +
+ "<dd>(TK;)T<del>0</del>V;</dd>" + // <= <strike>0</strike>
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ assertTrue("Annotation file should still exist", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/MyMap\n" +
+ "get\n" +
+ " (TK;)TV;\n" +
+ " (TK;)TV;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on an (outer) array type (in parameter position).
+ * The method already has a 2-line entry (i.e., not yet annotated).
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ public void testAnnotateParameter_Array1() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public String test(int[][] ints, List<String> list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ String initialContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n";
+ ensureExists(annotationFile.getParent());
+ annotationFile.create(new ByteArrayInputStream(initialContent.getBytes("UTF-8")), 0, null);
+
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("[][] ints");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as 'int @NonNull [][]'", list);
+ String expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([<b>1</b>[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as 'int @Nullable [][]'", list);
+ expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([<b>0</b>[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n" +
+ " ([0[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on a simple field type (type variable).
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ // FIXME(stephan): enable once implemented
+ public void _testAnnotateField1() throws Exception {
+
+ String NODE_PATH= "pack/age/Node";
+ String[] pathAndContents= new String[] {
+ NODE_PATH+".java",
+ "package pack.age;\n" +
+ "public class Node<V> {\n" +
+ " V value;\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_5);
+ IType type= fJProject1.findType(NODE_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("V value");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as '@NonNull V'", list);
+ String expectedInfo=
+ "<dl><dt>value</dt>" +
+ "<dd>TV;</dd>" +
+ "<dd>T<b>1</b>V;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as '@Nullable V'", list);
+ expectedInfo=
+ "<dl><dt>value</dt>" +
+ "<dd>TV;</dd>" +
+ "<dd>T<b>0</b>V;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(NODE_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/Node\n" +
+ "value\n" +
+ " TV;\n" +
+ " T0V;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AnnotateAssistTest18.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AnnotateAssistTest18.java
new file mode 100644
index 0000000..02c1703
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AnnotateAssistTest18.java
@@ -0,0 +1,505 @@
+/*******************************************************************************
+ * Copyright (c) 2015 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.quickfix;
+
+import java.io.ByteArrayInputStream;
+import java.util.List;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.runtime.Path;
+
+import org.eclipse.core.resources.IFile;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jdt.ui.tests.core.Java18ProjectTestSetup;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
+
+public class AnnotateAssistTest18 extends AbstractAnnotateAssistTests {
+
+ protected static final String ANNOTATION_PATH= "annots";
+
+ protected static final Class<?> THIS= AnnotateAssistTest18.class;
+
+ public static Test suite() {
+ return setUpTest(new TestSuite(THIS));
+ }
+
+ public static Test setUpTest(Test test) {
+ return new Java18ProjectTestSetup(test);
+ }
+ public AnnotateAssistTest18(String name) {
+ super(name);
+ }
+
+ protected void setUp() throws Exception {
+ fJProject1= Java18ProjectTestSetup.getProject();
+ fJProject1.getProject().getFolder(ANNOTATION_PATH).create(true, true, null);
+ fJProject1.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on a type argument of a parameter.
+ * The parameterized type already has a @NonNull annotation.
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ public void testAnnotateParameter_TypeArgument() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public String test(int[] ints, List<String> list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ String initialContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n" +
+ " ([IL1java/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n";
+ ensureExists(annotationFile.getParent());
+ annotationFile.create(new ByteArrayInputStream(initialContent.getBytes("UTF-8")), 0, null);
+
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("String> list");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as '@NonNull String'", list);
+ String expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([IL1java/util/List<L<b>1</b>java/lang/String;>;)Ljava/lang/String;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as '@Nullable String'", list);
+ expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([IL1java/util/List<L<b>0</b>java/lang/String;>;)Ljava/lang/String;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n" +
+ " ([IL1java/util/List<L0java/lang/String;>;)Ljava/lang/String;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert NO proposals on the primitive leaf type of an array type.
+ * @throws Exception
+ */
+ public void testAnnotateParameter_ArrayOfPrimitive() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public String test(int[] ints, List<String> list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("int[]");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertNumberOfProposals(list, 0);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on a wildcard bound inside a parameter type
+ * The parameterized type and the wildcard already has a @NonNull annotation.
+ * Annotation entry already exists, with @NonNull on the wildcard itself.
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ public void testAnnotateParameter_WildcardBound() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public String test(Object[] objects, List<? extends Number> list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ String initialContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([Ljava/lang/Object;Ljava/util/List<+Ljava/lang/Number;>;)Ljava/lang/String;\n" +
+ " ([Ljava/lang/Object;L1java/util/List<+1Ljava/lang/Number;>;)Ljava/lang/String;\n";
+ ensureExists(annotationFile.getParent());
+ annotationFile.create(new ByteArrayInputStream(initialContent.getBytes("UTF-8")), 0, null);
+
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("Number> list");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as '@NonNull Number'", list);
+ String expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([Ljava/lang/Object;Ljava/util/List<+Ljava/lang/Number;>;)Ljava/lang/String;</dd>" +
+ "<dd>([Ljava/lang/Object;L1java/util/List<+1L<b>1</b>java/lang/Number;>;)Ljava/lang/String;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as '@Nullable Number'", list);
+ expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([Ljava/lang/Object;Ljava/util/List<+Ljava/lang/Number;>;)Ljava/lang/String;</dd>" +
+ "<dd>([Ljava/lang/Object;L1java/util/List<+1L<b>0</b>java/lang/Number;>;)Ljava/lang/String;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([Ljava/lang/Object;Ljava/util/List<+Ljava/lang/Number;>;)Ljava/lang/String;\n" +
+ " ([Ljava/lang/Object;L1java/util/List<+1L0java/lang/Number;>;)Ljava/lang/String;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on an inner array type (in parameter position).
+ * A single line entry using this selector exists and will be amended.
+ * Apply the second proposal and check the effect.
+ *
+ * Cf. {@link AnnotateAssistTest15#testAnnotateParameter_Array1()}
+ * @throws Exception
+ */
+ public void testAnnotateParameter_Array2() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public String test(int[][] ints, List<String> list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ String initialContent=
+ "class pack/age/X\n" +
+ "test\n";
+ ensureExists(annotationFile.getParent());
+ annotationFile.create(new ByteArrayInputStream(initialContent.getBytes("UTF-8")), 0, null);
+
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("[] ints");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as 'int[] @NonNull []'", list);
+ String expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([[<b>1</b>ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as 'int[] @Nullable []'", list);
+ expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([[<b>0</b>ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n" +
+ " ([[0ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on an inner array type (in parameter position).
+ * An entry with annotation on the outer array already exists and will be amended.
+ * Apply the second proposal and check the effect.
+ *
+ * Cf. {@link AnnotateAssistTest15#testAnnotateParameter_Array1()}
+ * @throws Exception
+ */
+ public void testAnnotateParameter_Array3() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public String test(int[][] ints, List<String> list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ String initialContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n" +
+ " ([1[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n";
+ ensureExists(annotationFile.getParent());
+ annotationFile.create(new ByteArrayInputStream(initialContent.getBytes("UTF-8")), 0, null);
+
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("[] ints");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as 'int[] @NonNull []'", list);
+ String expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([1[<b>1</b>ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as 'int[] @Nullable []'", list);
+ expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd>([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" +
+ "<dd>([1[<b>0</b>ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " ([[ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n" +
+ " ([1[0ILjava/util/List<Ljava/lang/String;>;)Ljava/lang/String;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on a method's type parameter
+ * The parameterized type and the wildcard already has a @NonNull annotation.
+ * Annotation entry already exists, with @NonNull on the wildcard itself.
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ // FIXME(stephan): enable once implemented
+ public void _testAnnotateParameter_TypeParameter() throws Exception {
+
+ String X_PATH= "pack/age/X";
+ String[] pathAndContents= new String[] {
+ X_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public interface X {\n" +
+ " public <X, T extends List<X>> X test(T list);\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_8);
+
+ IType type= fJProject1.findType(X_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("T extends");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as '@NonNull T'", list);
+ String expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd><X:Ljava/lang/Object;T:Ljava/util/List<TX;>>(TT;)TX;</dd>" +
+ "<dd><X:Ljava/lang/Object;1T:Ljava/util/List<TX;>>(TT;)TX;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as '@Nullable Number'", list);
+ expectedInfo=
+ "<dl><dt>test</dt>" +
+ "<dd><X:Ljava/lang/Object;T:Ljava/util/List<TX;>>(TT;)TX;</dd>" +
+ "<dd><X:Ljava/lang/Object;0T:Ljava/util/List<TX;>>(TT;)TX;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(X_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/X\n" +
+ "test\n" +
+ " <X:Ljava/lang/Object;T:Ljava/util/List<;TX;>>(TT;)TX;\n" +
+ " <X:Ljava/lang/Object;0T:Ljava/util/List<;TX;>>(TT;)TX;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+
+ /**
+ * Assert two proposals ("@NonNull" and "@Nullable") on a complex field type (list of array)
+ * Apply the second proposal and check the effect.
+ * @throws Exception
+ */
+ // FIXME(stephan): enable once implemented
+ public void _testAnnotateField2() throws Exception {
+
+ String NODE_PATH= "pack/age/Node";
+ String[] pathAndContents= new String[] {
+ NODE_PATH+".java",
+ "package pack.age;\n" +
+ "import java.util.List;\n" +
+ "public class Node {\n" +
+ " List<Object[]> value;\n" +
+ "}\n"
+ };
+ addLibrary(fJProject1, "lib.jar", "lib.zip", pathAndContents, ANNOTATION_PATH, JavaCore.VERSION_1_5);
+ IType type= fJProject1.findType(NODE_PATH.replace('/', '.'));
+ JavaEditor javaEditor= (JavaEditor) JavaUI.openInEditor(type);
+
+ try {
+ int offset= pathAndContents[1].indexOf("[]> value");
+
+ List<ICompletionProposal> list= collectAnnotateProposals(javaEditor, offset);
+
+ assertCorrectLabels(list);
+ assertNumberOfProposals(list, 2);
+
+ ICompletionProposal proposal= findProposalByName("Annotate as 'Object @NonNull []'", list);
+ String expectedInfo=
+ "<dl><dt>value</dt>" +
+ "<dd>Ljava/util/List<[Ljava/lang/Object;>;</dd>" +
+ "<dd>Ljava/util/List<[<b>1</b>Ljava/lang/Object;>;</dd>" + // <= 1
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ proposal= findProposalByName("Annotate as 'Object @Nullable []'", list);
+ expectedInfo=
+ "<dl><dt>value</dt>" +
+ "<dd>Ljava/util/List<[Ljava/lang/Object;>;</dd>" +
+ "<dd>Ljava/util/List<[<b>0</b>Ljava/lang/Object;>;</dd>" + // <= 0
+ "</dl>";
+ assertEquals("expect detail", expectedInfo, proposal.getAdditionalProposalInfo());
+
+ IDocument document= javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
+ proposal.apply(document);
+
+ IFile annotationFile= fJProject1.getProject().getFile(new Path(ANNOTATION_PATH).append(NODE_PATH+".eea"));
+ assertTrue("Annotation file should have been created", annotationFile.exists());
+
+ String expectedContent=
+ "class pack/age/Node\n" +
+ "value\n" +
+ " Ljava/util/List<[Ljava/lang/Object;>;\n" +
+ " Ljava/util/List<[0Ljava/lang/Object;>;\n";
+ checkContentOfFile("annotation file content", annotationFile, expectedContent);
+ } finally {
+ JavaPlugin.getActivePage().closeAllEditors(false);
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/JarUtil.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/JarUtil.java
new file mode 100644
index 0000000..275f2b2
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/JarUtil.java
@@ -0,0 +1,717 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Nina Rinskaya
+ * Fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=172820.
+ * Stephan Herrmann - Contribution for
+ * [null] "Annotate" proposals for adding external null annotations to library classes - https://bugs.eclipse.org/458200
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.quickfix;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+
+/**
+ * This is a reduced and marginally adjusted copy from org.eclipse.jdt.core.tests.util.Util
+ */
+@SuppressWarnings("javadoc")
+public class JarUtil {
+
+ // Trace for delete operation
+ /*
+ * Maximum time wasted repeating delete operations while running JDT/Core tests.
+ */
+ private static int DELETE_MAX_TIME = 0;
+ /**
+ * Trace deletion operations while running JDT/Core tests.
+ */
+ public static boolean DELETE_DEBUG = false;
+ /**
+ * Maximum of time in ms to wait in deletion operation while running JDT/Core tests.
+ * Default is 10 seconds. This number cannot exceed 1 minute (ie. 60000).
+ * <br>
+ * To avoid too many loops while waiting, the ten first ones are done waiting
+ * 10ms before repeating, the ten loops after are done waiting 100ms and
+ * the other loops are done waiting 1s...
+ */
+ public static int DELETE_MAX_WAIT = 10000;
+
+ /**
+ * Initially, output directory was located in System.getProperty("java.io.tmpdir")+"\comptest".
+ * To allow user to run several compiler tests at the same time, main output directory
+ * is now located in a sub-directory of "comptest" which name is "run."+<code>System.currentMilliseconds</code>.
+ *
+ * @see #DELAY_BEFORE_CLEAN_PREVIOUS
+ */
+ private final static String OUTPUT_DIRECTORY;
+ /**
+ * Let user specify the delay in hours before output directories are removed from file system
+ * while starting a new test run. Default value is 2 hours.
+ * <p>
+ * Note that this value may be a float and so have time less than one hour.
+ * If value is 0 or negative, then all previous run directories will be removed...
+ *
+ * @see #OUTPUT_DIRECTORY
+ */
+ private final static String DELAY_BEFORE_CLEAN_PREVIOUS = System.getProperty("delay");
+ /*
+ * Static initializer to clean directories created while running previous test suites.
+ */
+ static {
+ // Get delay for cleaning sub-directories
+ long millisecondsPerHour = 1000L * 3600L;
+ long delay = millisecondsPerHour * 2; // default is to keep previous run directories for 2 hours
+ try {
+ if (DELAY_BEFORE_CLEAN_PREVIOUS != null) {
+ float hours = Float.parseFloat(DELAY_BEFORE_CLEAN_PREVIOUS);
+ delay = (int) (millisecondsPerHour * hours);
+ }
+ }
+ catch (NumberFormatException nfe) {
+ // use default
+ }
+
+ // Get output directory root from system properties
+ String container = System.getProperty("jdt.test.output_directory");
+ if (container == null){
+ container = System.getProperty("java.io.tmpdir");
+ }
+ if (container == null) {
+ container = "."; // use current directory
+ }
+
+ // Get file for root directory
+ if (Character.isLowerCase(container.charAt(0)) && container.charAt(1) == ':') {
+ container = Character.toUpperCase(container.charAt(0)) + container.substring(1);
+ }
+ File dir = new File(new File(container), "comptest");
+
+ // If root directory already exists, clean it
+ if (dir.exists()) {
+ long now = System.currentTimeMillis();
+ if ((now - dir.lastModified()) > delay) {
+ // remove all directory content
+ flushDirectoryContent(dir);
+ } else {
+ // remove only old sub-dirs
+ File[] testDirs = dir.listFiles();
+ for (int i=0,l=testDirs.length; i<l; i++) {
+ if (testDirs[i].isDirectory()) {
+ if ((now - testDirs[i].lastModified()) > delay) {
+ delete(testDirs[i]);
+ }
+ }
+ }
+ }
+ }
+
+ // Computed test run directory name based on current time
+ File dateDir = new File(dir, "run."+System.currentTimeMillis());
+ String pathDir = null;
+ try {
+ pathDir = dateDir.getCanonicalPath();
+ } catch (IOException e) {
+ pathDir = dateDir.getAbsolutePath();
+ }
+ OUTPUT_DIRECTORY = pathDir;
+ }
+
+
+public static CompilationUnit[] compilationUnits(String[] testFiles) {
+ int length = testFiles.length / 2;
+ CompilationUnit[] result = new CompilationUnit[length];
+ int index = 0;
+ for (int i = 0; i < length; i++) {
+ result[i] = new CompilationUnit(testFiles[index + 1].toCharArray(), testFiles[index], null);
+ index += 2;
+ }
+ return result;
+}
+/* inlined and simplified for JDT/UI */
+private static class Requestor implements ICompilerRequestor {
+ public boolean hasErrors = false;
+ public String outputPath;
+ public String problemLog = "";
+
+ public void acceptResult(CompilationResult compilationResult) {
+ this.hasErrors |= compilationResult.hasErrors();
+ this.problemLog += compilationResult.toString();
+ outputClassFiles(compilationResult);
+ }
+ protected void outputClassFiles(CompilationResult unitResult) {
+ if ((unitResult != null) && !unitResult.hasErrors()) {
+ ClassFile[] classFiles = unitResult.getClassFiles();
+ if (this.outputPath != null) {
+ for (int i = 0, fileCount = classFiles.length; i < fileCount; i++) {
+ // retrieve the key and the corresponding classfile
+ ClassFile classFile = classFiles[i];
+ String relativeName =
+ new String(classFile.fileName()).replace('/', File.separatorChar) + ".class";
+ try {
+ org.eclipse.jdt.internal.compiler.util.Util.writeToDisk(true, this.outputPath, relativeName, classFile);
+ } catch(IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+}
+public static void compile(String[] pathsAndContents, Map options, String[] classpath, String outputPath) {
+ IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault());
+ Requestor requestor = new Requestor();
+ requestor.outputPath = outputPath.endsWith(File.separator) ? outputPath : outputPath + File.separator;
+
+ String[] classLibs = getJavaClassLibs();
+ if (classpath != null) {
+ int length = classpath.length;
+ int classLibsLength = classLibs.length;
+ System.arraycopy(classpath, 0, classpath = new String[length + classLibsLength], 0, length);
+ System.arraycopy(classLibs, 0, classpath, length, classLibsLength);
+ } else {
+ classpath = classLibs;
+ }
+
+ INameEnvironment nameEnvironment = new FileSystem(classpath, new String[] {}, null);
+ IErrorHandlingPolicy errorHandlingPolicy =
+ new IErrorHandlingPolicy() {
+ public boolean proceedOnErrors() {
+ return true;
+ }
+ public boolean stopOnFirstError() {
+ return false;
+ }
+ public boolean ignoreAllErrors() {
+ return false;
+ }
+ };
+ CompilerOptions compilerOptions = new CompilerOptions(options);
+ compilerOptions.performMethodsFullRecovery = false;
+ compilerOptions.performStatementsRecovery = false;
+ Compiler batchCompiler =
+ new Compiler(
+ nameEnvironment,
+ errorHandlingPolicy,
+ compilerOptions,
+ requestor,
+ problemFactory);
+ batchCompiler.options.produceReferenceInfo = true;
+ batchCompiler.compile(compilationUnits(pathsAndContents)); // compile all files together
+ // cleanup
+ nameEnvironment.cleanup();
+ if (requestor.hasErrors)
+ System.err.print(requestor.problemLog); // problem log empty if no problems
+}
+public static void createFile(String path, String contents) throws IOException {
+ FileOutputStream output = new FileOutputStream(path);
+ try {
+ output.write(contents.getBytes());
+ } finally {
+ output.close();
+ }
+}
+public static void createJar(String[] pathsAndContents, String[] extraPathsAndContents, Map options, String[] classpath, String jarPath) throws IOException {
+ String classesPath = getOutputDirectory() + File.separator + "classes";
+ File classesDir = new File(classesPath);
+ flushDirectoryContent(classesDir);
+ if (pathsAndContents != null) {
+ compile(pathsAndContents, options, classpath, classesPath);
+ }
+ if (extraPathsAndContents != null) {
+ for (int i = 0, l = extraPathsAndContents.length; i < l; /* inc in loop */) {
+ File outputFile = new File(classesPath, extraPathsAndContents[i++]);
+ outputFile.getParentFile().mkdirs();
+ JarUtil.writeToFile(extraPathsAndContents[i++], outputFile.getAbsolutePath());
+ }
+ }
+ zip(classesDir, jarPath);
+}
+public static void createJar(String[] javaPathsAndContents, String[] extraPathsAndContents, String jarPath, String[] classpath, String compliance, Map options) throws IOException {
+ Map compileOptions = getCompileOptions(compliance);
+ if (options != null) {
+ compileOptions.putAll(options);
+ }
+ createJar(javaPathsAndContents, extraPathsAndContents, compileOptions, classpath, jarPath);
+}
+public static void createSourceZip(String[] pathsAndContents, String zipPath) throws IOException {
+ String sourcesPath = getOutputDirectory() + File.separator + "sources";
+ createSourceDir(pathsAndContents, sourcesPath);
+ zip(new File(sourcesPath), zipPath);
+}
+
+public static void createSourceDir(String[] pathsAndContents, String sourcesPath) throws IOException {
+ flushDirectoryContent(new File(sourcesPath));
+ for (int i = 0, length = pathsAndContents.length; i < length; i+=2) {
+ String sourcePath = sourcesPath + File.separator + pathsAndContents[i];
+ File sourceFile = new File(sourcePath);
+ sourceFile.getParentFile().mkdirs();
+ createFile(sourcePath, pathsAndContents[i+1]);
+ }
+}
+/**
+ * Delete a file or directory and insure that the file is no longer present
+ * on file system. In case of directory, delete all the hierarchy underneath.
+ *
+ * @param file The file or directory to delete
+ * @return true iff the file was really delete, false otherwise
+ */
+public static boolean delete(File file) {
+ // flush all directory content
+ if (file.isDirectory()) {
+ flushDirectoryContent(file);
+ }
+ // remove file
+ file.delete();
+ if (isFileDeleted(file)) {
+ return true;
+ }
+ return waitUntilFileDeleted(file);
+}
+/**
+ * Flush content of a given directory (leaving it empty),
+ * no-op if not a directory.
+ */
+public static void flushDirectoryContent(File dir) {
+ File[] files = dir.listFiles();
+ if (files == null) return;
+ for (int i = 0, max = files.length; i < max; i++) {
+ delete(files[i]);
+ }
+}
+private static Map getCompileOptions(String compliance) {
+ Map options = new HashMap();
+ options.put(CompilerOptions.OPTION_Compliance, compliance);
+ options.put(CompilerOptions.OPTION_Source, compliance);
+ options.put(CompilerOptions.OPTION_TargetPlatform, compliance);
+ return options;
+}
+/**
+ * Search the user hard-drive for a Java class library.
+ * Returns null if none could be found.
+*/
+public static String[] getJavaClassLibs() {
+ // check bootclasspath properties for Sun, JRockit and Harmony VMs
+ String bootclasspathProperty = System.getProperty("sun.boot.class.path"); //$NON-NLS-1$
+ if ((bootclasspathProperty == null) || (bootclasspathProperty.length() == 0)) {
+ // IBM J9 VMs
+ bootclasspathProperty = System.getProperty("vm.boot.class.path"); //$NON-NLS-1$
+ if ((bootclasspathProperty == null) || (bootclasspathProperty.length() == 0)) {
+ // Harmony using IBM VME
+ bootclasspathProperty = System.getProperty("org.apache.harmony.boot.class.path"); //$NON-NLS-1$
+ }
+ }
+ String[] jars = null;
+ if ((bootclasspathProperty != null) && (bootclasspathProperty.length() != 0)) {
+ StringTokenizer tokenizer = new StringTokenizer(bootclasspathProperty, File.pathSeparator);
+ final int size = tokenizer.countTokens();
+ jars = new String[size];
+ int i = 0;
+ while (tokenizer.hasMoreTokens()) {
+ final String fileName = toNativePath(tokenizer.nextToken());
+ if (new File(fileName).exists()) {
+ jars[i] = fileName;
+ i++;
+ }
+ }
+ if (size != i) {
+ // resize
+ System.arraycopy(jars, 0, (jars = new String[i]), 0, i);
+ }
+ } else {
+ String jreDir = getJREDirectory();
+ final String osName = System.getProperty("os.name");
+ if (jreDir == null) {
+ return new String[] {};
+ }
+ if (osName.startsWith("Mac")) {
+ return new String[] {
+ toNativePath(jreDir + "/../Classes/classes.jar")
+ };
+ }
+ final String vmName = System.getProperty("java.vm.name");
+ if ("J9".equals(vmName)) {
+ return new String[] {
+ toNativePath(jreDir + "/lib/jclMax/classes.zip")
+ };
+ }
+ String[] jarsNames = null;
+ ArrayList paths = new ArrayList();
+ if ("DRLVM".equals(vmName)) {
+ FilenameFilter jarFilter = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".jar") & !name.endsWith("-src.jar");
+ }
+ };
+ jarsNames = new File(jreDir + "/lib/boot/").list(jarFilter);
+ addJarEntries(jreDir + "/lib/boot/", jarsNames, paths);
+ } else {
+ jarsNames = new String[] {
+ "/lib/vm.jar",
+ "/lib/rt.jar",
+ "/lib/core.jar",
+ "/lib/security.jar",
+ "/lib/xml.jar",
+ "/lib/graphics.jar"
+ };
+ addJarEntries(jreDir, jarsNames, paths);
+ }
+ jars = new String[paths.size()];
+ paths.toArray(jars);
+ }
+ return jars;
+}
+private static void addJarEntries(String jreDir, String[] jarNames, ArrayList paths) {
+ for (int i = 0, max = jarNames.length; i < max; i++) {
+ final String currentName = jreDir + jarNames[i];
+ File f = new File(currentName);
+ if (f.exists()) {
+ paths.add(toNativePath(currentName));
+ }
+ }
+}
+/**
+ * Returns the JRE directory this tests are running on.
+ * Returns null if none could be found.
+ *
+ * Example of use: [org.eclipse.jdt.core.tests.util.Util.getJREDirectory()]
+ */
+public static String getJREDirectory() {
+ return System.getProperty("java.home");
+}
+/**
+ * Search the user hard-drive for a possible output directory.
+ * Returns null if none could be found.
+ *
+ * Example of use: [org.eclipse.jdt.core.tests.util.Util.getOutputDirectory()]
+ */
+public static String getOutputDirectory() {
+ return OUTPUT_DIRECTORY;
+}
+/**
+ * Returns the parent's child file matching the given file or null if not found.
+ *
+ * @param file The searched file in parent
+ * @return The parent's child matching the given file or null if not found.
+ */
+private static File getParentChildFile(File file) {
+ File parent = file.getParentFile();
+ if (parent == null || !parent.exists()) return null;
+ File[] files = parent.listFiles();
+ int length = files==null ? 0 : files.length;
+ if (length > 0) {
+ for (int i=0; i<length; i++) {
+ if (files[i] == file) {
+ return files[i];
+ } else if (files[i].equals(file)) {
+ return files[i];
+ } else if (files[i].getPath().equals(file.getPath())) {
+ return files[i];
+ }
+ }
+ }
+ return null;
+}
+/**
+ * Returns the test name from stack elements info.
+ *
+ * @return The name of the test currently running
+ */
+private static String getTestName() {
+ StackTraceElement[] elements = new Exception().getStackTrace();
+ int idx = 0, length=elements.length;
+ while (idx<length && !elements[idx++].getClassName().startsWith("org.eclipse.jdt")) {
+ // loop until JDT/Core class appears in the stack
+ }
+ if (idx<length) {
+ StackTraceElement testElement = null;
+ while (idx<length && elements[idx].getClassName().startsWith("org.eclipse.jdt")) {
+ testElement = elements[idx++];
+ }
+ if (testElement != null) {
+ return testElement.getClassName() + " - " + testElement.getMethodName();
+ }
+ }
+ return "?";
+}
+/**
+ * Returns whether a file is really deleted or not.
+ * Does not only rely on {@link File#exists()} method but also
+ * look if it's not in its parent children {@link #getParentChildFile(File)}.
+ *
+ * @param file The file to test if deleted
+ * @return true if the file does not exist and was not found in its parent children.
+ */
+public static boolean isFileDeleted(File file) {
+ return !file.exists() && getParentChildFile(file) == null;
+}
+/**
+ * Print given file information with specified indentation.
+ * These information are:<ul>
+ * <li>read {@link File#canRead()}</li>
+ * <li>write {@link File#canWrite()}</li>
+ * <li>exists {@link File#exists()}</li>
+ * <li>is file {@link File#isFile()}</li>
+ * <li>is directory {@link File#isDirectory()}</li>
+ * <li>is hidden {@link File#isHidden()}</li>
+ * </ul>
+ * May recurse several level in parents hierarchy.
+ * May also display children, but then will not recusre in parent
+ * hierarchy to avoid infinite loop...
+ *
+ * @param file The file to display information
+ * @param indent Number of tab to print before the information
+ * @param recurse Display also information on <code>recurse</code>th parents in hierarchy.
+ * If negative then display children information instead.
+ */
+private static void printFileInfo(File file, int indent, int recurse) {
+ String tab = "";
+ for (int i=0; i<indent; i++) tab+="\t";
+ System.out.print(tab+"- "+file.getName()+" file info: ");
+ String sep = "";
+ if (file.canRead()) {
+ System.out.print("read");
+ sep = ", ";
+ }
+ if (file.canWrite()) {
+ System.out.print(sep+"write");
+ sep = ", ";
+ }
+ if (file.exists()) {
+ System.out.print(sep+"exist");
+ sep = ", ";
+ }
+ if (file.isDirectory()) {
+ System.out.print(sep+"dir");
+ sep = ", ";
+ }
+ if (file.isFile()) {
+ System.out.print(sep+"file");
+ sep = ", ";
+ }
+ if (file.isHidden()) {
+ System.out.print(sep+"hidden");
+ sep = ", ";
+ }
+ System.out.println();
+ File[] files = file.listFiles();
+ int length = files==null ? 0 : files.length;
+ if (length > 0) {
+ boolean children = recurse < 0;
+ System.out.print(tab+" + children: ");
+ if (children) System.out.println();
+ for (int i=0; i<length; i++) {
+ if (children) { // display children
+ printFileInfo(files[i], indent+2, -1);
+ } else {
+ if (i>0) System.out.print(", ");
+ System.out.print(files[i].getName());
+ if (files[i].isDirectory()) System.out.print("[dir]");
+ else if (files[i].isFile()) System.out.print("[file]");
+ else System.out.print("[?]");
+ }
+ }
+ if (!children) System.out.println();
+ }
+ if (recurse > 0) {
+ File parent = file.getParentFile();
+ if (parent != null) printFileInfo(parent, indent+1, recurse-1);
+ }
+}
+/**
+ * Print stack trace with only JDT elements.
+ *
+ * @param exception Exception of the stack trace. May be null, then a fake exception is used.
+ * @param indent Number of tab to display before the stack elements to display.
+ */
+private static void printJdtStackTrace(Exception exception, int indent) {
+ String tab = "";
+ for (int i=0; i<indent; i++) tab+="\t";
+ StackTraceElement[] elements = (exception==null?new Exception():exception).getStackTrace();
+ int idx = 0, length=elements.length;
+ while (idx<length && !elements[idx++].getClassName().startsWith("org.eclipse.jdt")) {
+ // loop until JDT/Core class appears in the stack
+ }
+ if (idx<length) {
+ System.out.print(tab+"- stack trace");
+ if (exception == null)
+ System.out.println(":");
+ else
+ System.out.println(" for exception "+exception+":");
+ while (idx<length && elements[idx].getClassName().startsWith("org.eclipse.jdt")) {
+ StackTraceElement testElement = elements[idx++];
+ System.out.println(tab+" -> "+testElement);
+ }
+ } else {
+ exception.printStackTrace(System.out);
+ }
+}
+/**
+ * Makes the given path a path using native path separators as returned by File.getPath()
+ * and trimming any extra slash.
+ */
+public static String toNativePath(String path) {
+ String nativePath = path.replace('\\', File.separatorChar).replace('/', File.separatorChar);
+ return
+ nativePath.endsWith("/") || nativePath.endsWith("\\") ?
+ nativePath.substring(0, nativePath.length() - 1) :
+ nativePath;
+}
+public static void waitAtLeast(int time) {
+ long start = System.currentTimeMillis();
+ do {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ }
+ } while ((System.currentTimeMillis() - start) < time);
+}
+
+/**
+ * Wait until the file is _really_ deleted on file system.
+ *
+ * @param file Deleted file
+ * @return true if the file was finally deleted, false otherwise
+ */
+private static boolean waitUntilFileDeleted(File file) {
+ if (DELETE_DEBUG) {
+ System.out.println();
+ System.out.println("WARNING in test: "+getTestName());
+ System.out.println(" - problems occured while deleting "+file);
+ printJdtStackTrace(null, 1);
+ printFileInfo(file.getParentFile(), 1, -1); // display parent with its children
+ System.out.print(" - wait for ("+DELETE_MAX_WAIT+"ms max): ");
+ }
+ int count = 0;
+ int delay = 10; // ms
+ int maxRetry = DELETE_MAX_WAIT / delay;
+ int time = 0;
+ while (count < maxRetry) {
+ try {
+ count++;
+ Thread.sleep(delay);
+ time += delay;
+ if (time > DELETE_MAX_TIME) DELETE_MAX_TIME = time;
+ if (DELETE_DEBUG) System.out.print('.');
+ if (file.exists()) {
+ if (file.delete()) {
+ // SUCCESS
+ if (DELETE_DEBUG) {
+ System.out.println();
+ System.out.println(" => file really removed after "+time+"ms (max="+DELETE_MAX_TIME+"ms)");
+ System.out.println();
+ }
+ return true;
+ }
+ }
+ if (isFileDeleted(file)) {
+ // SUCCESS
+ if (DELETE_DEBUG) {
+ System.out.println();
+ System.out.println(" => file disappeared after "+time+"ms (max="+DELETE_MAX_TIME+"ms)");
+ System.out.println();
+ }
+ return true;
+ }
+ // Increment waiting delay exponentially
+ if (count >= 10 && delay <= 100) {
+ count = 1;
+ delay *= 10;
+ maxRetry = DELETE_MAX_WAIT / delay;
+ if ((DELETE_MAX_WAIT%delay) != 0) {
+ maxRetry++;
+ }
+ }
+ }
+ catch (InterruptedException ie) {
+ break; // end loop
+ }
+ }
+ if (!DELETE_DEBUG) {
+ System.out.println();
+ System.out.println("WARNING in test: "+getTestName());
+ System.out.println(" - problems occured while deleting "+file);
+ printJdtStackTrace(null, 1);
+ printFileInfo(file.getParentFile(), 1, -1); // display parent with its children
+ }
+ System.out.println();
+ System.out.println(" !!! ERROR: "+file+" was never deleted even after having waited "+DELETE_MAX_TIME+"ms!!!");
+ System.out.println();
+ return false;
+}
+public static void writeToFile(String contents, String destinationFilePath) {
+ File destFile = new File(destinationFilePath);
+ FileOutputStream output = null;
+ PrintWriter writer = null;
+ try {
+ output = new FileOutputStream(destFile);
+ writer = new PrintWriter(output);
+ writer.print(contents);
+ writer.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ } finally {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+}
+public static void zip(File rootDir, String zipPath) throws IOException {
+ ZipOutputStream zip = null;
+ try {
+ File zipFile = new File(zipPath);
+ if (zipFile.exists()) {
+ if (!delete(zipFile))
+ throw new IOException("Could not delete " + zipPath);
+ // ensure the new zip file has a different timestamp than the previous one
+ int timeToWait = 1000; // some platform (like Linux) have a 1s granularity)
+ waitAtLeast(timeToWait);
+ } else {
+ zipFile.getParentFile().mkdirs();
+ }
+ zip = new ZipOutputStream(new FileOutputStream(zipFile));
+ zip(rootDir, zip, rootDir.getPath().length()+1); // 1 for last slash
+ } finally {
+ if (zip != null) {
+ zip.close();
+ }
+ }
+}
+private static void zip(File dir, ZipOutputStream zip, int rootPathLength) throws IOException {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (int i = 0, length = files.length; i < length; i++) {
+ File file = files[i];
+ if (file.isFile()) {
+ String path = file.getPath();
+ path = path.substring(rootPathLength);
+ ZipEntry entry = new ZipEntry(path.replace('\\', '/'));
+ zip.putNextEntry(entry);
+ zip.write(org.eclipse.jdt.internal.compiler.util.Util.getFileByteContent(file));
+ zip.closeEntry();
+ } else {
+ zip(file, zip, rootPathLength);
+ }
+ }
+ }
+}
+}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/QuickFixTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/QuickFixTest.java
index fe4fe90..e05a3f4 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/QuickFixTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/QuickFixTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -104,6 +104,8 @@
suite.addTest(TypeParameterMismatchTest.suite());
suite.addTest(PropertiesFileQuickAssistTest.suite());
suite.addTest(NullAnnotationsQuickFixTest.suite());
+ suite.addTest(AnnotateAssistTest15.suite());
+ suite.addTest(AnnotateAssistTest18.suite());
return new ProjectTestSetup(suite);
}
diff --git a/org.eclipse.jdt.ui/META-INF/MANIFEST.MF b/org.eclipse.jdt.ui/META-INF/MANIFEST.MF
index a7da5b5..b5d2725 100644
--- a/org.eclipse.jdt.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.ui/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.jdt.ui; singleton:=true
-Bundle-Version: 3.10.100.qualifier
+Bundle-Version: 3.11.0.qualifier
Bundle-Activator: org.eclipse.jdt.internal.ui.JavaPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/ExternalNullAnnoatationChangeProposals.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/ExternalNullAnnoatationChangeProposals.java
new file mode 100644
index 0000000..63af60c
--- /dev/null
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/ExternalNullAnnoatationChangeProposals.java
@@ -0,0 +1,616 @@
+/*******************************************************************************
+ * Copyright (c) 2015 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.corext.fix;
+
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NONNULL;
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NO_ANNOTATION;
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NULLABLE;
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMethodParameterType;
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMethodReturnType;
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.extractGenericSignature;
+import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.getAnnotationFile;
+import static org.eclipse.jdt.internal.ui.text.spelling.WordCorrectionProposal.getHtmlRepresentation;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.ArrayType;
+import org.eclipse.jdt.core.dom.Dimension;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.ParameterizedType;
+import org.eclipse.jdt.core.dom.PrimitiveType;
+import org.eclipse.jdt.core.dom.SimpleType;
+import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
+import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
+import org.eclipse.jdt.core.dom.Type;
+import org.eclipse.jdt.core.dom.TypeParameter;
+import org.eclipse.jdt.core.dom.WildcardType;
+import org.eclipse.jdt.core.util.ExternalAnnotationUtil;
+import org.eclipse.jdt.core.util.ExternalAnnotationUtil.MergeStrategy;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+import org.eclipse.jdt.internal.corext.util.Messages;
+
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.JavaPluginImages;
+import org.eclipse.jdt.internal.ui.text.correction.ExternalNullAnnotationQuickAssistProcessor;
+import org.eclipse.jdt.internal.ui.text.correction.IProposalRelevance;
+
+/**
+ * Proposals for null annotations that modify external annotations, rather than Java source files.
+ *
+ * @see <a href="https://bugs.eclipse.org/458200">[null] "Annotate" proposals for adding external
+ * null annotations to library classes</a>
+ * @since 3.11
+ */
+public class ExternalNullAnnoatationChangeProposals {
+
+ static abstract class SignatureAnnotationChangeProposal implements IJavaCompletionProposal, ICommandAccess {
+
+ protected String fLabel;
+
+ protected ICompilationUnit fCU; // cu where the assist was invoked
+
+ protected String fAffectedTypeName;
+
+ protected IFile fAnnotationFile;
+
+ protected String fSelector;
+
+ protected String fSignature;
+
+ protected String fCurrentAnnotated;
+
+ protected String fAnnotatedSignature;
+
+ protected MergeStrategy fMergeStrategy;
+
+ protected String[] fDryRun; // result from a dry-run signature update; structure: { prefix, old-type, new-type, postfix }
+
+
+ /* return true if the operation is available. */
+ protected boolean initializeOperation(ICompilationUnit cu, ITypeBinding declaringClass, String selector,
+ String plainSignature, String annotatedSignature, String label, MergeStrategy mergeStrategy) {
+ IJavaProject project= (IJavaProject) cu.getAncestor(IJavaElement.JAVA_PROJECT);
+ IFile file= null;
+ try {
+ file= getAnnotationFile(project, declaringClass, new NullProgressMonitor());
+ } catch (CoreException e) {
+ return false;
+ }
+ if (file == null)
+ return false;
+
+ fCU= cu;
+ fAffectedTypeName= declaringClass.getErasure().getBinaryName().replace('.', '/');
+ fAnnotationFile= file;
+ fSelector= selector;
+ fAnnotatedSignature= annotatedSignature;
+ fSignature= plainSignature;
+
+ fLabel= label;
+ fMergeStrategy= mergeStrategy;
+
+ fCurrentAnnotated= ExternalAnnotationUtil.getAnnotatedSignature(fAffectedTypeName, file, fSelector, fSignature);
+ if (fCurrentAnnotated == null)
+ fCurrentAnnotated= fSignature;
+ dryRun();
+ return fDryRun != null && !fDryRun[1].equals(fDryRun[2]);
+ }
+
+ /**
+ * Perform a dry-run annotation update, to check if we have any update, indeed. If
+ * successful, the result should be available in {@link #fDryRun}.
+ */
+ protected abstract void dryRun();
+
+ public Point getSelection(IDocument document) {
+ return null; // nothing to reveal in the current editor.
+ }
+
+ public String getDisplayString() {
+ return fLabel;
+ }
+
+ public Image getImage() {
+ return JavaPluginImages.get(JavaPluginImages.IMG_OBJS_ANNOTATION);
+ }
+
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+
+ public void apply(IDocument document) {
+ try {
+ doAnnotateMember(new NullProgressMonitor());
+ } catch (CoreException e) {
+ JavaPlugin.log(e);
+ } catch (IOException e) {
+ JavaPlugin.log(e);
+ }
+ }
+
+ public int getRelevance() {
+ return IProposalRelevance.CHANGE_METHOD;
+ }
+
+ public String getCommandId() {
+ return ExternalNullAnnotationQuickAssistProcessor.ANNOTATE_MEMBER_ID;
+ }
+
+ public String getAdditionalProposalInfo() {
+ StringBuffer buffer= new StringBuffer();
+ buffer.append("<dl>"); //$NON-NLS-1$
+ buffer.append("<dt>").append(fSelector).append("</dt>"); //$NON-NLS-1$ //$NON-NLS-2$
+ buffer.append("<dd>").append(getHtmlRepresentation(fSignature)).append("</dd>"); //$NON-NLS-1$ //$NON-NLS-2$
+ buffer.append("<dd>").append(getFullAnnotatedSignatureHTML()).append("</dd>"); //$NON-NLS-1$ //$NON-NLS-2$
+ buffer.append("</dl>"); //$NON-NLS-1$
+ return buffer.toString();
+ }
+
+ protected String getFullAnnotatedSignatureHTML() {
+ String[] parts= fDryRun;
+
+ // search the difference:
+ int pos= 0;
+ while (pos < parts[1].length() && pos < parts[2].length()) {
+ if (parts[1].charAt(pos) != parts[2].charAt(pos))
+ break;
+ pos++;
+ }
+
+ // prefix up-to the difference:
+ StringBuilder buf= new StringBuilder();
+ buf.append(getHtmlRepresentation(parts[0]));
+ buf.append(getHtmlRepresentation(parts[2].substring(0, pos)));
+
+ // highlight the difference:
+ switch (parts[2].charAt(pos)) {
+ case NULLABLE:
+ case NONNULL:
+ // added annotation in parts[2]: bold:
+ buf.append("<b>").append(parts[2].charAt(pos)).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
+ break;
+ default:
+ // removed annotation in parts[1]: strike:
+ buf.append("<del>").append(parts[1].charAt(pos)).append("</del>"); //$NON-NLS-1$ //$NON-NLS-2$
+ pos--; // char in parts[2] is not yet consumed
+ }
+
+ // everything else:
+ buf.append(getHtmlRepresentation(parts[2].substring(pos + 1)));
+ buf.append(getHtmlRepresentation(parts[3]));
+ return buf.toString();
+ }
+
+ protected abstract void doAnnotateMember(IProgressMonitor monitor) throws CoreException, UnsupportedEncodingException, IOException;
+ }
+
+ static class ReturnAnnotationRewriteProposal extends SignatureAnnotationChangeProposal {
+
+ @Override
+ protected void dryRun() {
+ fDryRun= ExternalAnnotationUtil.annotateReturnType(fCurrentAnnotated, fAnnotatedSignature, fMergeStrategy);
+ }
+
+ @Override
+ protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, IOException {
+ annotateMethodReturnType(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fMergeStrategy, monitor);
+ }
+ }
+
+ static class ParameterAnnotationRewriteProposal extends SignatureAnnotationChangeProposal {
+
+ int fParamIdx;
+
+ ParameterAnnotationRewriteProposal(int paramIdx) {
+ fParamIdx= paramIdx;
+ }
+
+ @Override
+ protected void dryRun() {
+ fDryRun= ExternalAnnotationUtil.annotateParameterType(fCurrentAnnotated, fAnnotatedSignature, fParamIdx, fMergeStrategy);
+ }
+
+ @Override
+ protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, IOException {
+ annotateMethodParameterType(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fParamIdx, fMergeStrategy, monitor);
+ }
+ }
+
+ /* Quick assist on class file, propose changes an any type detail. */
+ public static void collectExternalAnnotationProposals(ICompilationUnit cu, ASTNode coveringNode, int offset, ArrayList<IJavaCompletionProposal> resultingCollection) {
+
+ IJavaProject javaProject= cu.getJavaProject();
+ if (JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, true)))
+ return;
+
+ if (!hasAnnotationPathInWorkspace(javaProject, cu)) // refuse to update files outside the workspace
+ return;
+
+ ASTNode inner= null; // the innermost type or type parameter node
+ while (true) {
+ if (coveringNode instanceof Type || coveringNode instanceof TypeParameter) {
+ inner= coveringNode;
+ break;
+ }
+ coveringNode= coveringNode.getParent();
+ if (coveringNode == null)
+ return;
+ }
+ if (inner.getNodeType() == ASTNode.PRIMITIVE_TYPE)
+ return; // cannot be annotated
+
+ // prepare three renderers for three proposals:
+ TypeRenderer rendererNonNull= new TypeRenderer(inner, offset, NONNULL);
+ TypeRenderer rendererNullable= new TypeRenderer(inner, offset, NULLABLE);
+ TypeRenderer rendererRemove= new TypeRenderer(inner, offset, NO_ANNOTATION);
+ ASTNode outer= inner; // will become the outermost type or type parameter node
+ {
+ ASTNode next;
+ while (((next= outer.getParent()) instanceof Type) || (next instanceof TypeParameter))
+ outer= next;
+ }
+ boolean useJava8= JavaModelUtil.is18OrHigher(javaProject.getOption(JavaCore.COMPILER_SOURCE, true));
+ if (!useJava8 && outer != inner) {
+ return; // below 1.8 we can only annotate the top type (not type parameter)
+ }
+ if (outer instanceof Type) {
+ ITypeBinding typeBinding= ((Type) outer).resolveBinding();
+ if (typeBinding != null && typeBinding.isPrimitive())
+ return;
+ outer.accept(rendererNonNull);
+ outer.accept(rendererNullable);
+ outer.accept(rendererRemove);
+ } else {
+ List<?> siblingList= (List<?>) outer.getParent().getStructuralProperty(outer.getLocationInParent());
+ rendererNonNull.visitTypeParameters(siblingList);
+ rendererNullable.visitTypeParameters(siblingList);
+ rendererRemove.visitTypeParameters(siblingList);
+ }
+
+ StructuralPropertyDescriptor locationInParent= outer.getLocationInParent();
+ ProposalCreator creator= null;
+ if (locationInParent == MethodDeclaration.RETURN_TYPE2_PROPERTY) {
+ MethodDeclaration method= (MethodDeclaration) ASTNodes.getParent(coveringNode, MethodDeclaration.class);
+ creator= new ReturnProposalCreator(cu, method.resolveBinding());
+ } else if (locationInParent == SingleVariableDeclaration.TYPE_PROPERTY) {
+ ASTNode param= outer.getParent();
+ if (param.getLocationInParent() == MethodDeclaration.PARAMETERS_PROPERTY) {
+ MethodDeclaration method= (MethodDeclaration) ASTNodes.getParent(coveringNode, MethodDeclaration.class);
+ int paramIdx= method.parameters().indexOf(param);
+ if (paramIdx != -1)
+ creator= new ParameterProposalCreator(cu, method.resolveBinding(), paramIdx);
+ }
+ }
+ if (creator != null) {
+ createProposalsForType(cu, inner, offset, rendererNonNull, rendererNullable, rendererRemove, creator, resultingCollection);
+ }
+ }
+
+ static boolean hasAnnotationPathInWorkspace(IJavaProject javaProject, ICompilationUnit cu) {
+ IPackageFragmentRoot root= (IPackageFragmentRoot) cu.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+ if (root != null) {
+ try {
+ IClasspathEntry resolvedClasspathEntry= root.getResolvedClasspathEntry();
+ for (IClasspathAttribute cpa : resolvedClasspathEntry.getExtraAttributes()) {
+ if (IClasspathAttribute.EXTERNAL_ANNOTATION_PATH.equals(cpa.getName())) {
+ Path annotationPath= new Path(cpa.getValue());
+ IProject project= javaProject.getProject();
+ if (project.exists(annotationPath))
+ return true;
+ IWorkspaceRoot wsRoot= project.getWorkspace().getRoot();
+ return wsRoot.exists(annotationPath);
+ }
+ }
+ } catch (JavaModelException jme) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private static abstract class ProposalCreator {
+
+ ICompilationUnit fCU;
+
+ ITypeBinding fDeclaringClass;
+
+ String fSelector;
+
+ String fSignature;
+
+ MergeStrategy fMergeStrategy= MergeStrategy.OVERWRITE_ANNOTATIONS;
+
+ ProposalCreator(ICompilationUnit cu, ITypeBinding declaringClass, String selector, String signature) {
+ fCU= cu;
+ fDeclaringClass= declaringClass;
+ fSelector= selector;
+ fSignature= signature;
+ }
+
+ SignatureAnnotationChangeProposal create(String annotatedSignature, String label) {
+ SignatureAnnotationChangeProposal operation= doCreate(annotatedSignature, label);
+ if (!operation.initializeOperation(fCU, fDeclaringClass, fSelector, fSignature, annotatedSignature, label, fMergeStrategy))
+ return null;
+ return operation;
+ }
+
+ abstract SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label);
+ }
+
+ private static class ReturnProposalCreator extends ProposalCreator {
+
+ ReturnProposalCreator(ICompilationUnit cu, IMethodBinding methodBinding) {
+ super(cu, methodBinding.getDeclaringClass(), methodBinding.getName(), extractGenericSignature(methodBinding));
+ }
+
+ @Override
+ SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) {
+ return new ReturnAnnotationRewriteProposal();
+ }
+ }
+
+ private static class ParameterProposalCreator extends ProposalCreator {
+ int fParamIdx;
+
+ ParameterProposalCreator(ICompilationUnit cu, IMethodBinding methodBinding, int paramIdx) {
+ super(cu, methodBinding.getDeclaringClass(), methodBinding.getName(), extractGenericSignature(methodBinding));
+ fParamIdx= paramIdx;
+ }
+
+ @Override
+ SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) {
+ return new ParameterAnnotationRewriteProposal(fParamIdx);
+ }
+ }
+
+ /* Create one proposal from each of the three given renderers. */
+ static void createProposalsForType(ICompilationUnit cu, ASTNode type, int offset,
+ TypeRenderer rendererNonNull, TypeRenderer rendererNullable, TypeRenderer rendererRemove,
+ ProposalCreator creator, ArrayList<IJavaCompletionProposal> resultingCollection) {
+ SignatureAnnotationChangeProposal operation;
+ String label;
+ // propose adding @NonNull:
+ label= getAddAnnotationLabel(NullAnnotationsFix.getNonNullAnnotationName(cu, true), type, offset);
+ operation= creator.create(rendererNonNull.getResult(), label);
+ if (operation != null)
+ resultingCollection.add(operation);
+
+ // propose adding @Nullable:
+ label= getAddAnnotationLabel(NullAnnotationsFix.getNullableAnnotationName(cu, true), type, offset);
+ operation= creator.create(rendererNullable.getResult(), label);
+ if (operation != null)
+ resultingCollection.add(operation);
+
+ // propose removing annotation:
+ label= Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_remove_nullness_annotation,
+ new String[] { type2String(type, offset) });
+ operation= creator.create(rendererRemove.getResult(), label);
+ if (operation != null)
+ resultingCollection.add(operation);
+ }
+
+ static String getAddAnnotationLabel(String annotationName, ASTNode type, int offset) {
+ if (type.getNodeType() == ASTNode.ARRAY_TYPE) {
+ // need to assemble special format with annotation attached to the selected dimension:
+ ArrayType arrayType= (ArrayType) type;
+ StringBuilder left= new StringBuilder(arrayType.getElementType().toString());
+ StringBuilder dimsRight= new StringBuilder();
+ @SuppressWarnings("rawtypes")
+ List dimensions= arrayType.dimensions();
+ for (int i= 0; i < dimensions.size(); i++) {
+ Dimension dimension= (Dimension) dimensions.get(i);
+ if (dimension.getStartPosition() + dimension.getLength() <= offset)
+ left.append("[]"); //$NON-NLS-1$
+ else
+ dimsRight.append("[]"); //$NON-NLS-1$
+ }
+ return Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_add_nullness_array_annotation,
+ new String[] { left.toString(), annotationName, dimsRight.toString() });
+ }
+ return Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_add_nullness_annotation,
+ new String[] { annotationName, type.toString() });
+ }
+
+ static String type2String(ASTNode type, int offset) {
+ if (type.getNodeType() == ASTNode.ARRAY_TYPE) {
+ ArrayType arrayType= (ArrayType) type;
+ StringBuilder buf= new StringBuilder(arrayType.getElementType().toString());
+ @SuppressWarnings("rawtypes")
+ List dimensions= arrayType.dimensions();
+ for (int i= 0; i < dimensions.size(); i++) {
+ Dimension dimension= (Dimension) dimensions.get(i);
+ if (dimension.getStartPosition() + dimension.getLength() > offset)
+ buf.append("[]"); //$NON-NLS-1$
+ }
+ return buf.toString();
+ }
+ return type.toString();
+ }
+
+ /**
+ * A visitor that renders an AST snippet representing a type or type parameter. For rendering
+ * the Eclipse External Annotation format is used, i.e., class file signatures with additions
+ * for null annotations.
+ * <p>
+ * In particular a given null annotation is inserted for the given focusType.
+ * </p>
+ */
+ static class TypeRenderer extends ASTVisitor {
+
+ StringBuffer fBuffer;
+
+ ASTNode fFocusType; // Type or TypeParameter
+
+ int fOffset;
+
+ char fAnnotation;
+
+ public TypeRenderer(ASTNode focusType, int offset, char annotation) {
+ fBuffer= new StringBuffer();
+ fFocusType= focusType;
+ fOffset= offset;
+ fAnnotation= annotation;
+ }
+
+ public String getResult() {
+ return fBuffer.toString();
+ }
+
+ /* Renders a type parameter list in angle brackets. */
+ public void visitTypeParameters(@SuppressWarnings("rawtypes") List parameters) {
+ fBuffer.append('<');
+ for (Object p : parameters)
+ ((TypeParameter) p).accept(this);
+ fBuffer.append('>');
+ }
+
+ @Override
+ public boolean visit(ParameterizedType type) {
+ fBuffer.append('L');
+ if (type == fFocusType || type.getType() == fFocusType)
+ fBuffer.append(fAnnotation);
+ fBuffer.append(binaryName(type.resolveBinding()));
+ fBuffer.append('<');
+ for (Object arg : type.typeArguments())
+ ((Type) arg).accept(this);
+ fBuffer.append('>');
+ fBuffer.append(';');
+ return false;
+ }
+
+ @Override
+ public boolean visit(WildcardType wildcard) {
+ Type bound= wildcard.getBound();
+ if (bound == null) {
+ fBuffer.append('*');
+ } else if (wildcard.isUpperBound()) {
+ fBuffer.append('+');
+ } else {
+ fBuffer.append('-');
+ }
+ if (wildcard == fFocusType)
+ fBuffer.append(fAnnotation);
+ if (bound != null)
+ bound.accept(this);
+ return false;
+ }
+
+ @Override
+ public boolean visit(ArrayType array) {
+ @SuppressWarnings("rawtypes")
+ List dimensions= array.dimensions();
+ boolean annotated= false;
+ for (int i= 0; i < dimensions.size(); i++) {
+ fBuffer.append('[');
+ Dimension dimension= (Dimension) dimensions.get(i);
+ if (!annotated && array == fFocusType && dimension.getStartPosition() + dimension.getLength() > fOffset) {
+ fBuffer.append(fAnnotation);
+ annotated= true;
+ }
+ }
+ array.getElementType().accept(this);
+ return false;
+ }
+
+ @Override
+ public boolean visit(TypeParameter parameter) {
+ if (parameter == fFocusType)
+ fBuffer.append(fAnnotation);
+ fBuffer.append(parameter.getName().getIdentifier());
+ Type classBound= null;
+ for (Object bound : parameter.typeBounds()) {
+ Type typeBound= (Type) bound;
+ if (typeBound.resolveBinding().isClass()) {
+ classBound= typeBound;
+ break;
+ }
+ }
+ if (classBound != null) {
+ fBuffer.append(':');
+ classBound.accept(this);
+ } else {
+ ITypeBinding typeBinding= parameter.resolveBinding();
+ fBuffer.append(":L").append(binaryName(typeBinding.getSuperclass())).append(';'); //$NON-NLS-1$
+ }
+ for (Object bound : parameter.typeBounds()) {
+ if (bound == classBound)
+ continue;
+ Type typeBound= (Type) bound;
+ fBuffer.append(':');
+ typeBound.accept(this);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(SimpleType type) {
+ ITypeBinding typeBinding= type.resolveBinding();
+ if (typeBinding.isTypeVariable()) {
+ fBuffer.append('T');
+ if (fFocusType == type)
+ fBuffer.append(fAnnotation);
+ fBuffer.append(typeBinding.getName()).append(';');
+ } else {
+ fBuffer.append('L');
+ if (fFocusType == type)
+ fBuffer.append(fAnnotation);
+ fBuffer.append(binaryName(typeBinding)).append(';');
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(PrimitiveType node) {
+ // not a legal focus type, but could be array element type
+ fBuffer.append(node.resolveBinding().getBinaryName());
+ return false;
+ }
+
+ String binaryName(ITypeBinding type) {
+ return type.getBinaryName().replace('.', '/');
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
index b4a62fd..3153d10 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -10,6 +10,7 @@
* Stephan Herrmann - Contributions for
* [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
* [quick fix] The fix change parameter type to @Nonnull generated a null change - https://bugs.eclipse.org/400668
+ * [null] "Annotate" proposals for adding external null annotations to library classes - https://bugs.eclipse.org/458200
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
@@ -130,6 +131,10 @@
public static String NullAnnotationsRewriteOperations_change_overridden_return_nullness;
public static String NullAnnotationsRewriteOperations_remove_redundant_nullness_annotation;
+ public static String ExternalNullAnnotationChangeProposals_add_nullness_annotation;
+ public static String ExternalNullAnnotationChangeProposals_add_nullness_array_annotation;
+ public static String ExternalNullAnnotationChangeProposals_remove_nullness_annotation;
+
public static String ExtractToNullCheckedLocalProposal_extractCheckedLocal_editName;
public static String ExtractToNullCheckedLocalProposal_extractToCheckedLocal_proposalName;
public static String ExtractToNullCheckedLocalProposal_todoHandleNullDescription;
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
index e1c4e3d..703f175 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
@@ -1,5 +1,5 @@
###############################################################################
-# Copyright (c) 2005, 2014 IBM Corporation and others.
+# Copyright (c) 2005, 2015 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
@@ -9,7 +9,8 @@
# IBM Corporation - initial API and implementation
# Stephan Herrmann - Contributions for
# [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
-# [quick fix] The fix change parameter type to @Nonnull generated a null change - https://bugs.eclipse.org/400668
+# [quick fix] The fix change parameter type to @Nonnull generated a null change - https://bugs.eclipse.org/400668
+# [null] "Annotate" proposals for adding external null annotations to library classes - https://bugs.eclipse.org/458200
###############################################################################
CleanUpRefactoring_Refactoring_name=Clean Up
CleanUpRefactoring_Initialize_message=Checking preconditions for project ''{0}''
@@ -117,6 +118,10 @@
NullAnnotationsRewriteOperations_remove_redundant_nullness_annotation=Remove redundant nullness annotation
+ExternalNullAnnotationChangeProposals_add_nullness_annotation=Annotate as ''@{0} {1}''
+ExternalNullAnnotationChangeProposals_add_nullness_array_annotation=Annotate as ''{0} @{1} {2}''
+ExternalNullAnnotationChangeProposals_remove_nullness_annotation=Remove nullness annotation from type ''{0}''
+
ExtractToNullCheckedLocalProposal_extractCheckedLocal_editName=Extract checked local
ExtractToNullCheckedLocalProposal_extractToCheckedLocal_proposalName=Extract to checked local variable
ExtractToNullCheckedLocalProposal_todoHandleNullDescription=handle null value
diff --git a/org.eclipse.jdt.ui/plugin.properties b/org.eclipse.jdt.ui/plugin.properties
index 5ba320d..b506a09 100644
--- a/org.eclipse.jdt.ui/plugin.properties
+++ b/org.eclipse.jdt.ui/plugin.properties
@@ -480,6 +480,9 @@
IndentAction.label= Correct &Indentation
+annotateClassFile.label= Annotate Class File
+annotateClassFile.description= Externally add Annotations to a Class File.
+
##########################################################################
# Javadoc Support
##########################################################################
@@ -611,6 +614,9 @@
context.editingJavaSource.name= Editing Java Source
context.editingJavaSource.description= Editing Java Source Context
+context.browsingJavaSource.name= Browsing attached Java Source
+context.browsingJavaSource.description= Browsing attached Java Source Context
+
context.editingPropertiesSource.name= Editing Properties Files
context.editingPropertiesSource.description= Editing Properties Files Context
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index dd19b6f..79c074d 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -3360,6 +3360,12 @@
id="org.eclipse.jdt.ui.javaEditorScope">
</context>
<context
+ name="%context.browsingJavaSource.name"
+ description="%context.browsingJavaSource.description"
+ parentId="org.eclipse.ui.textEditorScope"
+ id="org.eclipse.jdt.ui.classFileEditorScope">
+ </context>
+ <context
name="%context.editingPropertiesSource.name"
description="%context.editingPropertiesSource.description"
parentId="org.eclipse.ui.textEditorScope"
@@ -4186,6 +4192,13 @@
categoryId="org.eclipse.search.ui.category.search"
id="org.eclipse.jdt.ui.edit.text.java.search.implement.occurrences">
</command>
+<!-- annotate class file: -->
+ <command
+ name="%annotateClassFile.label"
+ description="%annotateClassFile.description"
+ categoryId="org.eclipse.jdt.ui.category.source"
+ id="org.eclipse.jdt.ui.edit.text.java.annotate.classFile">
+ </command>
</extension>
<extension
@@ -4793,6 +4806,12 @@
value="org.eclipse.jdt.ui.JavadocView">
</parameter>
</key>
+ <key
+ commandId="org.eclipse.jdt.ui.edit.text.java.annotate.classFile"
+ contextId="org.eclipse.jdt.ui.classFileEditorScope"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="M1+1">
+ </key>
</extension>
diff --git a/org.eclipse.jdt.ui/pom.xml b/org.eclipse.jdt.ui/pom.xml
index b063295..dc2ad3d 100644
--- a/org.eclipse.jdt.ui/pom.xml
+++ b/org.eclipse.jdt.ui/pom.xml
@@ -18,6 +18,6 @@
</parent>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.ui</artifactId>
- <version>3.10.100-SNAPSHOT</version>
+ <version>3.11.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/AnnotateClassFileAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/AnnotateClassFileAction.java
new file mode 100644
index 0000000..34e5862
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/AnnotateClassFileAction.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2015 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.javaeditor;
+
+import org.eclipse.jface.action.Action;
+
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.SourceViewer;
+
+/**
+ * Action to add external null annotations from a ClassFileEditor.
+ *
+ * @see <a href="https://bugs.eclipse.org/458201">[null] Offer new command "Annotate" on
+ * ClassFileEditor</a>
+ * @since 3.11
+ */
+public class AnnotateClassFileAction extends Action {
+
+ private final ClassFileEditor fEditor;
+
+ protected AnnotateClassFileAction(ClassFileEditor editor) {
+ super(JavaEditorMessages.AnnotateClassFile_label);
+ fEditor= editor;
+ }
+
+ @Override
+ public void run() {
+ ISourceViewer viewer= fEditor.getViewer();
+ if (viewer instanceof SourceViewer) {
+ SourceViewer sourceViewer= (SourceViewer) viewer;
+ if (sourceViewer.canDoOperation(JavaSourceViewer.ANNOTATE_CLASS_FILE))
+ sourceViewer.doOperation(JavaSourceViewer.ANNOTATE_CLASS_FILE);
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java
index eccd345..47ac163 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/ClassFileEditor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Contribution for Bug 458201 - Offer new command "Annotate" on ClassFileEditor
*******************************************************************************/
package org.eclipse.jdt.internal.ui.javaeditor;
@@ -54,6 +55,7 @@
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.text.IWidgetTokenKeeper;
+import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
@@ -64,6 +66,7 @@
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.ActionGroup;
+import org.eclipse.ui.navigator.ICommonMenuConstants;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
@@ -89,6 +92,7 @@
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.SharedASTProvider;
+import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
import org.eclipse.jdt.ui.actions.RefactorActionGroup;
import org.eclipse.jdt.ui.wizards.BuildPathDialogAccess;
@@ -547,6 +551,10 @@
fActionGroups.addGroup(group);
fContextMenuGroup= new CompositeActionGroup(new ActionGroup[] {group});
+ Action action= new AnnotateClassFileAction(this);
+ action.setActionDefinitionId(IJavaEditorActionDefinitionIds.ANNOTATE_CLASS_FILE);
+ setAction(IJavaEditorActionDefinitionIds.ANNOTATE_CLASS_FILE, action);
+
/*
* 1GF82PL: ITPJUI:ALL - Need to be able to add bookmark to class file
*
@@ -566,6 +574,9 @@
public void editorContextMenuAboutToShow(IMenuManager menu) {
super.editorContextMenuAboutToShow(menu);
+ IAction action = getAction(IJavaEditorActionDefinitionIds.ANNOTATE_CLASS_FILE);
+ menu.appendToGroup(ICommonMenuConstants.GROUP_EDIT, action);
+
ActionContext context= new ActionContext(getSelectionProvider().getSelection());
fContextMenuGroup.setContext(context);
fContextMenuGroup.fillContextMenu(menu);
@@ -573,6 +584,14 @@
}
/*
+ * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#initializeKeyBindingScopes()
+ */
+ @Override
+ protected void initializeKeyBindingScopes() {
+ setKeyBindingScopes(new String[] { "org.eclipse.jdt.ui.javaEditorScope", "org.eclipse.jdt.ui.classFileEditorScope" }); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /*
* @see JavaEditor#getElementAt(int)
*/
@Override
@@ -882,8 +901,9 @@
/*
* @see JavaEditor#createJavaSourceViewer(Composite, IVerticalRuler, int)
*/
- protected ISourceViewer createJavaSourceViewer(Composite parent, IVerticalRuler ruler, int styles, IPreferenceStore store) {
- return new JavaSourceViewer(parent, ruler, null, false, styles, store) {
+ @Override
+ protected ISourceViewer createJavaSourceViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean isOverviewRulerVisible, int styles, IPreferenceStore store) {
+ return new JavaSourceViewer(parent, ruler, overviewRuler, isOverviewRulerVisible, styles, store) {
@Override
public boolean requestWidgetToken(IWidgetTokenKeeper requester) {
@@ -898,6 +918,25 @@
return false;
return super.requestWidgetToken(requester, priority);
}
+
+ @Override
+ public boolean canDoOperation(int operation) {
+ if (operation == JavaSourceViewer.ANNOTATE_CLASS_FILE)
+ return true;
+ return super.canDoOperation(operation);
+ }
+
+ @Override
+ public void doOperation(int operation) {
+ if (operation == JavaSourceViewer.ANNOTATE_CLASS_FILE) {
+ fQuickAssistAssistant.setStatusLineVisible(true);
+ fQuickAssistAssistant.setStatusMessage(JavaEditorMessages.ClassFileEditor_changeExternalAnnotations_caption + ' ');
+ String msg= fQuickAssistAssistant.showPossibleQuickAssists();
+ setStatusLineErrorMessage(msg);
+ return;
+ }
+ super.doOperation(operation);
+ }
};
}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.java
index 36a54f6..02df16f 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2013 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -10,6 +10,7 @@
* Andre Soereng <andreis@fast.no> - [syntax highlighting] highlight numbers - https://bugs.eclipse.org/bugs/show_bug.cgi?id=63573
* Björn Michael <b.michael@gmx.de> - [syntax highlighting] Syntax coloring for abstract classes - https://bugs.eclipse.org/331311
* Björn Michael <b.michael@gmx.de> - [syntax highlighting] Add highlight for inherited fields - https://bugs.eclipse.org/348368
+ * Stephan Herrmann - Offer new command "Annotate" on ClassFileEditor - https://bugs.eclipse.org/458201
*******************************************************************************/
package org.eclipse.jdt.internal.ui.javaeditor;
@@ -48,6 +49,7 @@
public static String AddImportOnSelection_error_title;
public static String AddImportOnSelection_dialog_title;
public static String AddImportOnSelection_dialog_message;
+ public static String ClassFileEditor_changeExternalAnnotations_caption;
public static String ClassFileEditor_error_classfile_not_on_classpath;
public static String ClassFileEditor_error_invalid_input_message;
public static String ClassFileEditor_error_title;
@@ -165,6 +167,7 @@
public static String JavaElementReturnTypeHyperlink_error_msg;
public static String JavaElementSuperImplementationHyperlink_hyperlinkText;
public static String JavaElementSuperImplementationHyperlink_hyperlinkText_qualified;
+ public static String AnnotateClassFile_label;
static {
NLS.initializeMessages(BUNDLE_NAME, JavaEditorMessages.class);
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.properties
index 49a40f1..235b327 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditorMessages.properties
@@ -10,6 +10,7 @@
# Andre Soereng <andreis@fast.no> - [syntax highlighting] highlight numbers - https://bugs.eclipse.org/bugs/show_bug.cgi?id=63573
# Bjoern Michael <b.michael@gmx.de> - [syntax highlighting] Syntax coloring for abstract classes - https://bugs.eclipse.org/331311
# Bjoern Michael <b.michael@gmx.de> - [syntax highlighting] Add highlight for inherited fields - https://bugs.eclipse.org/348368
+# Stephan Herrmann - Offer new command "Annotate" on ClassFileEditor - https://bugs.eclipse.org/458201
###############################################################################
AddImportOnSelection_label=A&dd Import
@@ -21,6 +22,7 @@
AddImportOnSelection_dialog_title=Add Import
AddImportOnSelection_dialog_message=&Choose element to generate an import statement for:
+ClassFileEditor_changeExternalAnnotations_caption=Change external annotations
ClassFileEditor_error_classfile_not_on_classpath=The class file is not on the classpath
ClassFileEditor_SourceAttachmentForm_cannotconfigure=The JAR of this class file belongs to container ''{0}'' can not be configured.
ClassFileEditor_SourceAttachmentForm_notsupported=The JAR of this class file belongs to container ''{0}'' which does not support the attachment of sources to its entries.
@@ -157,3 +159,5 @@
Editor_OpenPropertiesFile_hyperlinkText= Open in ''{0}''
Editor_MoveLines_IllegalMove_status= Move not possible - Uncheck "Show Source of Selected Element Only" to see the entire document
+
+AnnotateClassFile_label=&Annotate
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaSourceViewer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaSourceViewer.java
index ba418b6..9c8bf2d 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaSourceViewer.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaSourceViewer.java
@@ -83,6 +83,11 @@
*/
public static final int SHOW_HIERARCHY= 53;
+ /**
+ * Operation code for the annotate class file action.
+ */
+ public static final int ANNOTATE_CLASS_FILE= 54;
+
private IInformationPresenter fOutlinePresenter;
private IInformationPresenter fStructurePresenter;
private IInformationPresenter fHierarchyPresenter;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/ExternalNullAnnotationQuickAssistProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/ExternalNullAnnotationQuickAssistProcessor.java
new file mode 100644
index 0000000..bf40680
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/ExternalNullAnnotationQuickAssistProcessor.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2015 GK Software AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.text.correction;
+
+import java.util.ArrayList;
+
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+
+import org.eclipse.ui.IEditorPart;
+
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.WorkingCopyOwner;
+
+import org.eclipse.jdt.internal.corext.fix.ExternalNullAnnoatationChangeProposals;
+
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput;
+
+
+/**
+ * Alternate, stripped down quick assist processor for the annotate class file action.
+ *
+ * @see <a href="https://bugs.eclipse.org/458200">[null] "Annotate" proposals for adding external
+ * null annotations to library classes</a>
+ * @since 3.11
+ */
+public class ExternalNullAnnotationQuickAssistProcessor implements IQuickAssistProcessor {
+
+ public static final String ANNOTATE_MEMBER_ID= "org.eclipse.jdt.ui.annotate.nullAnnotateMember.assist"; //$NON-NLS-1$
+
+ private JavaCorrectionAssistant fAssistant;
+
+ private String fErrorMessage;
+
+ public ExternalNullAnnotationQuickAssistProcessor(JavaCorrectionAssistant javaCorrectionAssistant) {
+ fAssistant= javaCorrectionAssistant;
+ }
+
+ public String getErrorMessage() {
+ return fErrorMessage;
+ }
+
+ public boolean canFix(Annotation annotation) {
+ return false; // irrelevant on class files
+ }
+
+ public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+ IEditorPart part= fAssistant.getEditor();
+ IClassFile classFile= ((IClassFileEditorInput) part.getEditorInput()).getClassFile();
+ return classFile.getJavaProject().getOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, true).equals(JavaCore.ENABLED);
+ }
+
+ public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext quickAssistContext) {
+ ICompilationUnit cu= null;
+ try {
+ // preparation similar to JavaCorrectionProcessor:
+ IEditorPart part= fAssistant.getEditor();
+
+ IClassFile classFile= ((IClassFileEditorInput) part.getEditorInput()).getClassFile();
+ cu= classFile.getWorkingCopy((WorkingCopyOwner) null, null);
+ if (cu == null || classFile.getSource() == null)
+ return null;
+
+ ISourceViewer viewer= quickAssistContext.getSourceViewer();
+ int documentOffset= quickAssistContext.getOffset();
+
+ AssistContext context= null;
+ int length= viewer != null ? viewer.getSelectedRange().y : 0;
+ context= new AssistContext(cu, viewer, part, documentOffset, length);
+
+ ArrayList<IJavaCompletionProposal> proposals= new ArrayList<IJavaCompletionProposal>();
+
+ // shortcut (we only have one processor):
+ ExternalNullAnnoatationChangeProposals.collectExternalAnnotationProposals(context.getCompilationUnit(),
+ context.getCoveringNode(), documentOffset, proposals);
+
+ return proposals.toArray(new IJavaCompletionProposal[proposals.size()]);
+
+ } catch (JavaModelException e) {
+ fErrorMessage= e.getMessage();
+ JavaPlugin.log(e);
+ return null;
+ } finally {
+ try {
+ if (cu != null)
+ cu.discardWorkingCopy();
+ } catch (JavaModelException e) {
+ // ignore
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/JavaCorrectionAssistant.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/JavaCorrectionAssistant.java
index 10ce6e0..4376cc2 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/JavaCorrectionAssistant.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/JavaCorrectionAssistant.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Stephan Herrmann - Offer new command "Annotate" on ClassFileEditor - https://bugs.eclipse.org/458201
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.correction;
@@ -30,6 +31,7 @@
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
@@ -49,6 +51,7 @@
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.ClassFileEditor;
public class JavaCorrectionAssistant extends QuickAssistAssistant {
@@ -72,7 +75,11 @@
Assert.isNotNull(editor);
fEditor= editor;
- JavaCorrectionProcessor processor= new JavaCorrectionProcessor(this);
+ IQuickAssistProcessor processor;
+ if (editor instanceof ClassFileEditor)
+ processor= new ExternalNullAnnotationQuickAssistProcessor(this);
+ else
+ processor= new JavaCorrectionProcessor(this);
setQuickAssistProcessor(processor);
enableColoredLabels(PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS));
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/actions/IJavaEditorActionDefinitionIds.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/actions/IJavaEditorActionDefinitionIds.java
index 997ed24..d2ae3cb 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/actions/IJavaEditorActionDefinitionIds.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/actions/IJavaEditorActionDefinitionIds.java
@@ -759,4 +759,11 @@
* @since 3.2
*/
public static final String CLEAN_UP= "org.eclipse.jdt.ui.edit.text.java.clean.up"; //$NON-NLS-1$
+
+ /**
+ * Action definition id of the annotate class file action
+ * (value: <code>"org.eclipse.jdt.ui.edit.text.java.annotate.classFile"</code>).
+ * @since 3.11
+ */
+ public static final String ANNOTATE_CLASS_FILE= "org.eclipse.jdt.ui.edit.text.java.annotate.classFile"; //$NON-NLS-1$
}