Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Glassmyer2015-02-06 17:37:10 +0000
committerMarkus Keller2015-02-20 13:18:47 +0000
commitb1e2ed2bb1403d1f729ccda27eb7b9ebe7009bed (patch)
treea8dc10fe364bb36b96bcd80db9bf96769a8678aa
parentf8f573fb4945f47fa2e1f634d0b55fa9e0151f17 (diff)
downloadeclipse.jdt.core-b1e2ed2bb1403d1f729ccda27eb7b9ebe7009bed.tar.gz
eclipse.jdt.core-b1e2ed2bb1403d1f729ccda27eb7b9ebe7009bed.tar.xz
eclipse.jdt.core-b1e2ed2bb1403d1f729ccda27eb7b9ebe7009bed.zip
Rewrite of ImportRewriteAnalyzer, fixing several issues.
Fixes the following Eclipse bugs: Bug 71761 - [import rewrite] ImportRewrite should let me add explicit import to existing on demand import Bug 318437 - Organize Imports ignores Number of Imports needed for .* Bug 360789 - Organize imports changes static imports to .* even when that introduces compile errors Bug 412929 - [organize import] Adding a type results in adding a package and later does not honor order Bug 430303 - import group sorting is broken Bug 457051 - comment is discarded when reducing imports to an on-demand import Bug 457089 - imports are improperly reordered in the presence of a floating comment Some changes in behavior, which are reflected in the tests: ImportRewriteAnalyzer no longer attempts to place unmatched imports into their "best match" import groups, instead always placing all unmatched imports together in a single match-all group. The relocation and/or removal of comments associated with removed imports has been made more consistent. The behavior of removing comments has been made consistent between the two rewrite modes (incremental mode and "Organize Imports" mode): if there is a corresponding on-demand import for a removed single import (or vice-versa), comments are moved there; otherwise they are removed entirely. (This behavior is also more consistent with the removal of comments when deleting e.g. a field or a type.) Comments relocated from single import(s) to an on-demand import (or vice-versa) are now always placed on line(s) preceding the destination import, even if they had been trailing comments. This behavior makes more sense especially in cases where multiple single imports are reduced into an on-demand import or vice-versa. The scope of the fix for bug 121428 has been reduced, in that comments after a package declaration are no longer treated as file header comments. This is clearly beneficial in testBug376930_4, where the leading comments of java.util.* are no longer apparently reassigned to java.io.*. Blank lines around and between comments are now preserved, e.g. in testBug378024c_1. This preserves existing formatting and prevents comments from becoming associated with different imports. Change-Id: I1ae4edb45f7dc19f4263410038a9d023ac2fe054 Signed-off-by: John Glassmyer <jogl@google.com>
-rw-r--r--org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewriteTest.java1847
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java167
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ImportRewriteAnalyzer.java1522
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictIdentifier.java158
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictingSimpleNameFinder.java34
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportAdder.java23
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComment.java30
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComparator.java70
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java42
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEditor.java536
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEntry.java38
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportGroupComparator.java197
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java95
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java664
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteConfiguration.java261
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportsDelta.java37
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/NewImportEntry.java35
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java120
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandReduction.java39
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OrderPreservingImportAdder.java175
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OriginalImportEntry.java79
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageAndContainingTypeImportComparator.java26
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageImportComparator.java78
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RemovedImportCommentReassigner.java186
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ReorderingImportAdder.java47
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RewriteSite.java60
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/StaticConflictingSimpleNameFinder.java82
-rw-r--r--org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/TypeConflictingSimpleNameFinder.java107
28 files changed, 4881 insertions, 1874 deletions
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewriteTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewriteTest.java
index 1f4dcb46c1..cee0f4ea79 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewriteTest.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewriteTest.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
@@ -8,14 +8,21 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - Contribution for Bug 378024 - Ordering of comments between imports not preserved
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
*******************************************************************************/
package org.eclipse.jdt.core.tests.rewrite.describing;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
import junit.framework.Test;
+import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.BindingKey;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
@@ -32,11 +39,13 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jdt.core.tests.model.AbstractJavaModelTests;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
+import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.osgi.service.prefs.BackingStoreException;
@@ -83,6 +92,8 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
proj.setOption(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS, String.valueOf(1));
+ // The tests in this class assume that the line separator is "\n".
+ new ProjectScope(proj.getProject()).getNode(Platform.PI_RUNTIME).put(Platform.PREF_LINE_SEPARATOR, "\n");
this.sourceFolder = getPackageFragmentRoot("P", "src");
@@ -94,6 +105,1310 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
super.tearDown();
}
+ /**
+ * Addresses https://bugs.eclipse.org/412929 ("Adding a type results in adding a package and
+ * later does not honor order").
+ */
+ public void testImportGroupMatchingQualifiedName() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ String[] order = new String[] { "#android.R.doFoo", "android.R", "java", "android" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 999, 999, false);
+ imports.setUseContextToFilterImplicitImports(true);
+ imports.addImport("android.R");
+ imports.addImport("java.util.List");
+ imports.addImport("android.Foo");
+ imports.addStaticImport("android.R", "doFoo", false);
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import static android.R.doFoo;\n");
+ expected.append("\n");
+ expected.append("import android.R;\n");
+ expected.append("\n");
+ expected.append("import java.util.List;\n");
+ expected.append("\n");
+ expected.append("import android.Foo;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that the comments from single imports are reassigned
+ * to a new on-demand import into which they are reduced.
+ */
+ public void testReduceNewOnDemand() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.io.Serializable;\n");
+ contents.append("\n");
+ contents.append("// A floating leading\n");
+ contents.append("\n");
+ contents.append("// A leading\n");
+ contents.append("/* A same-line leading */ import java.net.A; // A same-line trailing\n");
+ contents.append("// A trailing\n");
+ contents.append("\n");
+ contents.append("// B floating leading\n");
+ contents.append("\n");
+ contents.append("// B leading\n");
+ contents.append("/* B same-line leading */ import java.net.B; // B same-line trailing\n");
+ contents.append("// B trailing\n");
+ contents.append("\n");
+ contents.append("// C floating leading\n");
+ contents.append("\n");
+ contents.append("// C leading\n");
+ contents.append("/* C same-line leading */ import java.net.C; // C same-line trailing\n");
+ contents.append("// C trailing\n");
+ contents.append("\n");
+ contents.append("import java.util.List;\n");
+ contents.append("\n");
+ contents.append("public class Clazz {}");
+ ICompilationUnit cu = createCompilationUnit("pack1", "Clazz", contents.toString());
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 1, 1, false);
+ imports.setUseContextToFilterImplicitImports(true);
+ imports.addImport("java.io.Serializable");
+ imports.addImport("java.net.A");
+ imports.addImport("java.net.B");
+ imports.addImport("java.net.C");
+ imports.addImport("java.util.List");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.io.*;\n");
+ expected.append("\n");
+ expected.append("// A floating leading\n");
+ expected.append("\n");
+ expected.append("// A leading\n");
+ expected.append("/* A same-line leading */\n");
+ expected.append("// A same-line trailing\n");
+ expected.append("// A trailing\n");
+ expected.append("\n");
+ expected.append("// B floating leading\n");
+ expected.append("\n");
+ expected.append("// B leading\n");
+ expected.append("/* B same-line leading */\n");
+ expected.append("// B same-line trailing\n");
+ expected.append("// B trailing\n");
+ expected.append("\n");
+ expected.append("// C floating leading\n");
+ expected.append("\n");
+ expected.append("// C leading\n");
+ expected.append("/* C same-line leading */\n");
+ expected.append("// C same-line trailing\n");
+ expected.append("// C trailing\n");
+ expected.append("import java.net.*;\n");
+ expected.append("import java.util.*;\n");
+ expected.append("\n");
+ expected.append("public class Clazz {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that the comments from single imports are reassigned
+ * to an existing on-demand import into which they are reduced,
+ * and that the on-demand import's own comments are preserved.
+ */
+ public void testReduceExistingOnDemand() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.io.*;\n");
+ contents.append("\n");
+ contents.append("// on-demand floating\n");
+ contents.append("\n");
+ contents.append("// on-demand leading\n");
+ contents.append("/* on-demand same-line leading */ import java.net.*; // on-demand same-line trailing\n");
+ contents.append("// on-demand trailing\n");
+ contents.append("\n");
+ contents.append("// A floating leading\n");
+ contents.append("\n");
+ contents.append("// A leading\n");
+ contents.append("/* A same-line leading */ import java.net.A; // A same-line trailing\n");
+ contents.append("// A trailing\n");
+ contents.append("\n");
+ contents.append("// B floating leading\n");
+ contents.append("\n");
+ contents.append("// B leading\n");
+ contents.append("/* B same-line leading */ import java.net.B; // B same-line trailing\n");
+ contents.append("// B trailing\n");
+ contents.append("\n");
+ contents.append("// C floating leading\n");
+ contents.append("\n");
+ contents.append("// C leading\n");
+ contents.append("/* C same-line leading */ import java.net.C; // C same-line trailing\n");
+ contents.append("// C trailing\n");
+ contents.append("\n");
+ contents.append("import java.util.*;\n");
+ contents.append("\n");
+ contents.append("public class Clazz {}");
+ ICompilationUnit cu = createCompilationUnit("pack1", "Clazz", contents.toString());
+
+ String[] order = new String[] { "java.io", "java", "java.util" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 1, 1, false);
+ imports.setUseContextToFilterImplicitImports(true);
+ imports.addImport("java.io.Serializable");
+ imports.addImport("java.net.A");
+ imports.addImport("java.net.B");
+ imports.addImport("java.net.C");
+ imports.addImport("java.util.List");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.io.*;\n");
+ expected.append("\n");
+ expected.append("// A floating leading\n");
+ expected.append("\n");
+ expected.append("// A leading\n");
+ expected.append("/* A same-line leading */\n");
+ expected.append("// A same-line trailing\n");
+ expected.append("// A trailing\n");
+ expected.append("\n");
+ expected.append("// B floating leading\n");
+ expected.append("\n");
+ expected.append("// B leading\n");
+ expected.append("/* B same-line leading */\n");
+ expected.append("// B same-line trailing\n");
+ expected.append("// B trailing\n");
+ expected.append("\n");
+ expected.append("// C floating leading\n");
+ expected.append("\n");
+ expected.append("// C leading\n");
+ expected.append("/* C same-line leading */\n");
+ expected.append("// C same-line trailing\n");
+ expected.append("// C trailing\n");
+ expected.append("\n");
+ expected.append("// on-demand floating\n");
+ expected.append("\n");
+ expected.append("// on-demand leading\n");
+ expected.append("/* on-demand same-line leading */ import java.net.*; // on-demand same-line trailing\n");
+ expected.append("// on-demand trailing\n");
+ expected.append("\n");
+ expected.append("import java.util.*;\n");
+ expected.append("\n");
+ expected.append("public class Clazz {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that comments from an expanded on-demand import are reassigned
+ * to a corresponding single import, and that comments of other single imports
+ * with the same container name are preserved.
+ */
+ public void testExpandOnDemand() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1\n");
+ contents.append("\n");
+ contents.append("import com.example;\n");
+ contents.append("\n");
+ contents.append("/* on-demand floating */\n");
+ contents.append("\n");
+ contents.append("// on-demand leading\n");
+ contents.append("/* on-demand same-line leading */ import java.util.*; // on-demand same-line trailing\n");
+ contents.append("// on-demand trailing\n");
+ contents.append("\n");
+ contents.append("/* ArrayList floating */\n");
+ contents.append("\n");
+ contents.append("// ArrayList leading\n");
+ contents.append("/* ArrayList same-line leading */ import java.util.ArrayList; // ArrayList same-line trailing\n");
+ contents.append("// ArrayList trailing\n");
+ contents.append("\n");
+ contents.append("/* List floating */\n");
+ contents.append("\n");
+ contents.append("// List leading\n");
+ contents.append("/* List same-line leading */ import java.util.List; // List same-line trailing\n");
+ contents.append("// List trailing\n");
+ contents.append("\n");
+ contents.append("/* Map floating */\n");
+ contents.append("\n");
+ contents.append("// Map leading\n");
+ contents.append("/* Map same-line leading */ import java.util.Map; // Map same-line trailing\n");
+ contents.append("// Map trailing\n");
+ contents.append("\n");
+ contents.append("import java.net.Socket;\n");
+ contents.append("\n");
+ contents.append("public class C {}\n");
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "com", "java.util", "java.net" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 999, 999, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+ importRewrite.addImport("java.util.ArrayList");
+ importRewrite.addImport("java.util.Map");
+ importRewrite.addImport("java.util.Set");
+ importRewrite.addImport("java.net.Socket");
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1\n");
+ expected.append("\n");
+ expected.append("/* on-demand floating */\n");
+ expected.append("\n");
+ expected.append("// on-demand leading\n");
+ expected.append("/* on-demand same-line leading */\n");
+ expected.append("// on-demand same-line trailing\n");
+ expected.append("// on-demand trailing\n");
+ expected.append("\n");
+ expected.append("/* ArrayList floating */\n");
+ expected.append("\n");
+ expected.append("// ArrayList leading\n");
+ expected.append("/* ArrayList same-line leading */ import java.util.ArrayList; // ArrayList same-line trailing\n");
+ expected.append("// ArrayList trailing\n");
+ expected.append("\n");
+ expected.append("/* Map floating */\n");
+ expected.append("\n");
+ expected.append("// Map leading\n");
+ expected.append("/* Map same-line leading */ import java.util.Map; // Map same-line trailing\n");
+ expected.append("// Map trailing\n");
+ expected.append("import java.util.Set;\n");
+ expected.append("\n");
+ expected.append("import java.net.Socket;\n");
+ expected.append("\n");
+ expected.append("public class C {}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that the comments of a removed import (other than an expanded on-demand import with
+ * a corresponding single import, or a reduced single import with a correponding on-demand
+ * import) are removed.
+ */
+ public void testRemovedImportCommentsAreRemoved() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("/* Socket is a very useful class */\n");
+ contents.append("import java.net.Socket; // Socket to 'em!\n");
+ contents.append("/* Thank goodness Java has built-in networking libraries! */\n");
+ contents.append("\n");
+ contents.append("import java.util.ArrayList;\n");
+ contents.append("\n");
+ contents.append("public class C {}\n");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("java.util.ArrayList");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.util.ArrayList;\n");
+ expected.append("\n");
+ expected.append("public class C {}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Addresses https://bugs.eclipse.org/318437 ("Organize Imports ignores Number of Imports needed
+ * for .*") and https://bugs.eclipse.org/359724 ("nested type imports not collapsed to wildcards
+ * ('*')").
+ */
+ public void testOnDemandWithinType() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 1, 1, false);
+ imports.setUseContextToFilterImplicitImports(true);
+ imports.addImport("java.util.Map.Entry");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.util.Map.*;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that a comment embedded within an import declaration is preserved.
+ */
+ public void testCommentWithinImportDeclaration() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import /* comment */ java.util.Map.*;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 1, 1, false);
+ imports.setUseContextToFilterImplicitImports(true);
+ imports.addImport("java.util.Map.*");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import /* comment */ java.util.Map.*;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Addresses https://bugs.eclipse.org/457051 ("comment is discarded when reducing imports to an
+ * on-demand import").
+ */
+ public void testFloatingCommentPreservedWhenReducingOnDemandAbove() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.util.Queue;\n");
+ contents.append("\n");
+ contents.append("/* floating comment */\n");
+ contents.append("\n");
+ contents.append("import java.util.concurrent.BlockingDeque;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 2, 2, true);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+ importRewrite.addImport("java.util.Formatter");
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.util.*;\n");
+ expected.append("\n");
+ expected.append("/* floating comment */\n");
+ expected.append("\n");
+ expected.append("import java.util.concurrent.BlockingDeque;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Addresses https://bugs.eclipse.org/457089 ("imports are improperly reordered in the presence
+ * of a floating comment").
+ */
+ public void testFloatingCommentDoesntCauseImportsToMove() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.io.Serializable;\n");
+ contents.append("\n");
+ contents.append("/* floating comment */\n");
+ contents.append("\n");
+ contents.append("import java.util.List;\n");
+ contents.append("\n");
+ contents.append("import javax.sql.DataSource;\n");
+ contents.append("\n");
+ contents.append("public class C {}\n");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java", "javax" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 999, 999, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+ importRewrite.addImport("java.io.Serializable");
+ importRewrite.addImport("java.util.List");
+ importRewrite.addImport("javax.sql.DataSource");
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.io.Serializable;\n");
+ expected.append("\n");
+ expected.append("/* floating comment */\n");
+ expected.append("\n");
+ expected.append("import java.util.List;\n");
+ expected.append("\n");
+ expected.append("import javax.sql.DataSource;\n");
+ expected.append("\n");
+ expected.append("public class C {}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testAddImportIntoMatchAllImportGroup() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.util.ArrayList;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "", "java.net" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 999, 999, true);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+ importRewrite.addImport("java.net.Socket");
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.util.ArrayList;\n");
+ expected.append("\n");
+ expected.append("import java.net.Socket;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testCuInDefaultPackageWithNoExistingImports() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("public class C {}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java", "java.net" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 999, 999, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+ importRewrite.addImport("java.net.Socket");
+ importRewrite.addImport("java.util.ArrayList");
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("import java.util.ArrayList;\n");
+ expected.append("\n");
+ expected.append("import java.net.Socket;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Addresses https://bugs.eclipse.org/71761 ("ImportRewrite should let me add explicit import to
+ * existing on demand import").
+ */
+ public void testNeedsExplicitImport() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ String[] order = new String[] { "java" };
+
+ ImportRewriteContext needsExplicitImportContext = new ImportRewriteContext() {
+ public int findInContext(String qualifier, String name, int kind) {
+ return ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT;
+ }
+ };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 1, 1, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+ importRewrite.addStaticImport("java.util.Collections", "shuffle", false, needsExplicitImportContext);
+ importRewrite.addStaticImport("java.util.Collections", "sort", false);
+ importRewrite.addImport("java.util.List", needsExplicitImportContext);
+ importRewrite.addImport("java.util.Map");
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import static java.util.Collections.*;\n");
+ expected.append("import static java.util.Collections.shuffle;\n");
+ expected.append("\n");
+ expected.append("import java.util.*;\n");
+ expected.append("import java.util.List;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testOrganizeNoImportsWithOneLineDelim() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("public class C {}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 1, 1, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testOrganizeNoImportsWithTwoLineDelims() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 1, 1, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testOrganizeNoImportsWithJavadoc() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("/**\n");
+ contents.append(" * Best class ever.\n");
+ contents.append(" */\n");
+ contents.append("\n");
+ contents.append("public class C {\n}");
+
+ ICompilationUnit cu = createCompilationUnit("pack1", "C", contents.toString());
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite importRewrite = newImportsRewrite(cu, order, 1, 1, false);
+ importRewrite.setUseContextToFilterImplicitImports(true);
+
+ apply(importRewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("/**\n");
+ expected.append(" * Best class ever.\n");
+ expected.append(" */\n");
+ expected.append("\n");
+ expected.append("public class C {\n}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that imports are correctly placed after the end of a package declaration's multiline
+ * trailing comment.
+ */
+ public void testPackageDeclarationTrailingComment() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1; /* pack1 \n");
+ contents.append("trailing \n");
+ contents.append("comment */\n");
+ contents.append("\n");
+ contents.append("public class C {\n");
+ contents.append("}\n");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("java.util.ArrayList");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1; /* pack1 \n");
+ expected.append("trailing \n");
+ expected.append("comment */\n");
+ expected.append("\n");
+ expected.append("import java.util.ArrayList;\n");
+ expected.append("\n");
+ expected.append("public class C {\n");
+ expected.append("}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects correct placement of an import when package declaration, type declaration, and
+ * associated comments are all on one line.
+ */
+ public void testAddImportWithPackageAndTypeOnSameLine() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1; /* pack1 trailing */ /** C leading */ public class C {}\n");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] { "java" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("java.util.ArrayList");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1; /* pack1 trailing */\n");
+ expected.append("\n");
+ expected.append("import java.util.ArrayList;\n");
+ expected.append("\n");
+ expected.append("/** C leading */ public class C {}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that imports not matching defined import groups are placed together at the end.
+ *
+ * Addresses https://bugs.eclipse.org/430303 ("import group sorting is broken").
+ */
+ public void testUnmatchedImports() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ String[] order = new String[] { "java.net", "com.google" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("com.acme.BirdSeed");
+ imports.addImport("com.acme.Dynamite");
+ imports.addImport("com.google.Tgif");
+ imports.addImport("java.net.Socket");
+ imports.addImport("java.new.Bar");
+ imports.addImport("org.linux.Kernel");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.net.Socket;\n");
+ expected.append("\n");
+ expected.append("import com.google.Tgif;\n");
+ expected.append("\n");
+ expected.append("import com.acme.BirdSeed;\n");
+ expected.append("import com.acme.Dynamite;\n");
+ expected.append("import java.new.Bar;\n");
+ expected.append("import org.linux.Kernel;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that the order in which addImport is called does not affect the resulting order of
+ * import declarations.
+ *
+ * Addresses https://bugs.eclipse.org/430303 ("import group sorting is broken").
+ */
+ public void testAddImportsInVaryingOrder() throws Exception {
+ String[] order = new String[] { "h", "a" };
+
+ List importsToAdd = new ArrayList();
+ importsToAdd.add("a.ClassInA");
+ importsToAdd.add("b.ClassInB");
+ importsToAdd.add("c.ClassInC");
+ importsToAdd.add("d.ClassInD");
+ importsToAdd.add("e.ClassInE");
+ importsToAdd.add("f.ClassInF");
+ importsToAdd.add("g.ClassInG");
+ importsToAdd.add("h.ClassInH");
+
+ ICompilationUnit cu1 = createCompilationUnit("pack1", "C");
+ ImportRewrite imports1 = newImportsRewrite(cu1, order, 99, 99, false);
+ for (Iterator importsToAddIter = importsToAdd.iterator(); importsToAddIter.hasNext(); ) {
+ imports1.addImport((String) importsToAddIter.next());
+ }
+ apply(imports1);
+ String source1 = cu1.getSource();
+
+ Collections.reverse(importsToAdd);
+
+ ICompilationUnit cu2 = createCompilationUnit("pack1", "C");
+ ImportRewrite imports2 = newImportsRewrite(cu2, order, 99, 99, false);
+ for (Iterator importsToAddIter = importsToAdd.iterator(); importsToAddIter.hasNext(); ) {
+ imports2.addImport((String) importsToAddIter.next());
+ }
+ apply(imports2);
+ String source2 = cu2.getSource();
+
+ // Reversing the order in which imports are added via addImport() should not affect the rewritten order.
+ assertEqualString(source2, source1);
+ }
+
+ /**
+ * Expects that static imports not matching any defined import group end up above defined import
+ * groups and that non-static imports not matching any defined import group end up below defined
+ * import groups.
+ *
+ * Addresses https://bugs.eclipse.org/430303 ("import group sorting is broken").
+ */
+ public void testStaticAndNonStaticUnmatchedImports() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ String[] order = new String[] { "#a", "h" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addStaticImport("a.ClassInA", "staticMethodInA", false);
+ imports.addStaticImport("b.ClassInB", "staticMethodInB", false);
+ imports.addImport("g.ClassInG");
+ imports.addImport("h.ClassInH");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import static b.ClassInB.staticMethodInB;\n");
+ expected.append("\n");
+ expected.append("import static a.ClassInA.staticMethodInA;\n");
+ expected.append("\n");
+ expected.append("import h.ClassInH;\n");
+ expected.append("\n");
+ expected.append("import g.ClassInG;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expect that two duplicate on-demand imports and their comments survive a rewrite.
+ */
+ public void testAddWithDuplicateOnDemandImports() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.lang.*;\n");
+ contents.append("\n");
+ contents.append("/* foo.bar.* 1 leading */\n");
+ contents.append("/* foo.bar.* 1 same-line leading */ import foo.bar.*; // foo.bar.* 1 same-line trailing\n");
+ contents.append("/* foo.bar.* 1 trailing */\n");
+ contents.append("\n");
+ contents.append("import pack1.*;\n");
+ contents.append("\n");
+ contents.append("/* foo.bar.* 2 leading */\n");
+ contents.append("/* foo.bar.* 2 same-line leading */ import foo.bar.*; // foo.bar.* 2 same-line trailing\n");
+ contents.append("/* foo.bar.* 2 trailing */\n");
+ contents.append("\n");
+ contents.append("public class C {}\n");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] { "java.lang", "foo", "pack1", "com" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, true);
+ imports.addImport("com.example.MyClass");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.lang.*;\n");
+ expected.append("\n");
+ expected.append("/* foo.bar.* 1 leading */\n");
+ expected.append("/* foo.bar.* 1 same-line leading */ import foo.bar.*; // foo.bar.* 1 same-line trailing\n");
+ expected.append("/* foo.bar.* 1 trailing */\n");
+ expected.append("\n");
+ expected.append("import pack1.*;\n");
+ expected.append("\n");
+ expected.append("import com.example.MyClass;\n");
+ expected.append("\n");
+ expected.append("/* foo.bar.* 2 leading */\n");
+ expected.append("/* foo.bar.* 2 same-line leading */ import foo.bar.*; // foo.bar.* 2 same-line trailing\n");
+ expected.append("/* foo.bar.* 2 trailing */\n");
+ expected.append("\n");
+ expected.append("public class C {}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expect that two duplicate single imports and their comments survive a rewrite.
+ */
+ public void testAddWithDuplicateSingleImports() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import java.lang.*;\n");
+ contents.append("\n");
+ contents.append("/* foo.Bar 1 leading */\n");
+ contents.append("/* foo.Bar 1 same-line leading */ import foo.Bar; // foo.Bar 1 same-line trailing\n");
+ contents.append("/* foo.Bar 1 trailing */\n");
+ contents.append("\n");
+ contents.append("import pack1.*;\n");
+ contents.append("\n");
+ contents.append("/* foo.Bar 2 leading */\n");
+ contents.append("/* foo.Bar 2 same-line leading */ import foo.Bar; // foo.Bar 2 same-line trailing\n");
+ contents.append("/* foo.Bar 2 trailing */\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] { "java.lang", "foo", "pack1", "com" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, true);
+ imports.addImport("com.example.MyClass");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import java.lang.*;\n");
+ expected.append("\n");
+ expected.append("/* foo.Bar 1 leading */\n");
+ expected.append("/* foo.Bar 1 same-line leading */ import foo.Bar; // foo.Bar 1 same-line trailing\n");
+ expected.append("/* foo.Bar 1 trailing */\n");
+ expected.append("\n");
+ expected.append("import pack1.*;\n");
+ expected.append("\n");
+ expected.append("import com.example.MyClass;\n");
+ expected.append("\n");
+ expected.append("/* foo.Bar 2 leading */\n");
+ expected.append("/* foo.Bar 2 same-line leading */ import foo.Bar; // foo.Bar 2 same-line trailing\n");
+ expected.append("/* foo.Bar 2 trailing */\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testOtherDuplicateImportsNotDisturbed() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import pack1.SomeClass; // first import\n");
+ contents.append("import java.util.ArrayList;\n");
+ contents.append("\n");
+ contents.append("import pack1.SomeClass; // second import\n");
+ contents.append("import com.mycompany.Frobnigator;\n");
+ contents.append("\n");
+ contents.append("import pack1.SomeClass; // third import\n");
+ contents.append("import org.eclipse.GreatIde;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] { "java", "pack1", "com", "org" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, true);
+ imports.addImport("com.mycompany.Foo");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import pack1.SomeClass; // first import\n");
+ expected.append("import java.util.ArrayList;\n");
+ expected.append("\n");
+ expected.append("import pack1.SomeClass; // second import\n");
+ expected.append("\n");
+ expected.append("import com.mycompany.Foo;\n");
+ expected.append("import com.mycompany.Frobnigator;\n");
+ expected.append("\n");
+ expected.append("import pack1.SomeClass; // third import\n");
+ expected.append("import org.eclipse.GreatIde;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testDuplicateImportsDoNotCountTowardOnDemandThreshold() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import com.mycompany.Foo;\n");
+ contents.append("import com.mycompany.Foo;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] {};
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 3, 3, true);
+ imports.addImport("com.mycompany.Bar");
+
+ apply(imports);
+
+ // Expect that the 3-import on-demand threshold has not been reached.
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import com.mycompany.Bar;\n");
+ expected.append("import com.mycompany.Foo;\n");
+ expected.append("import com.mycompany.Foo;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that a conflict between identically named fields from two static on-demand imports
+ * is resolved with an explicit import of one of the fields.
+ *
+ * Addresses https://bugs.eclipse.org/360789 ("Organize imports changes static imports to .*
+ * even when that introduces compile errors").
+ */
+ public void testOnDemandConflictBetweenStaticFields() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ // This test uses enum constants because the example in bug 360789 used enum constants,
+ // but the behavior generalizes to static fields that are not enum constants.
+ IPackageFragment pack2 = this.sourceFolder.createPackageFragment("pack2", false, null);
+ StringBuffer horizontalEnum = new StringBuffer();
+ horizontalEnum.append("package pack2;\n");
+ horizontalEnum.append("public enum Horizontal { LEFT, CENTER, RIGHT }\n");
+ pack2.createCompilationUnit("Horizontal.java", horizontalEnum.toString(), false, null);
+ StringBuffer verticalEnum = new StringBuffer();
+ verticalEnum.append("package pack2;\n");
+ verticalEnum.append("public enum Vertical { TOP, CENTER, BOTTOM }\n");
+ pack2.createCompilationUnit("Vertical.java", verticalEnum.toString(), false, null);
+
+ String[] order = new String[] {};
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 1, 1, false);
+ imports.addStaticImport("pack2.Horizontal", "CENTER", true);
+ imports.addStaticImport("pack2.Vertical", "TOP", true);
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import static pack2.Horizontal.CENTER;\n");
+ expected.append("import static pack2.Vertical.*;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that a conflict between a static on-demand import and a type on-demand import
+ * is resolved with an explicit import of one of the conflicting member types.
+ *
+ * Inspired by https://bugs.eclipse.org/360789
+ */
+ public void testOnDemandConflictBetweenTypeAndNestedStaticType() throws Exception {
+ ICompilationUnit cu = createCompilationUnit("pack1", "C");
+
+ IPackageFragment pack2 = this.sourceFolder.createPackageFragment("pack2", false, null);
+ StringBuffer containingType = new StringBuffer();
+ containingType.append("package pack2;\n");
+ containingType.append("public class ContainingType {\n");
+ containingType.append(" public static class TypeWithSameName {}\n");
+ containingType.append(" public static final int CONSTANT = 42;\n");
+ containingType.append("}\n");
+ pack2.createCompilationUnit("ContainingType.java", containingType.toString(), false, null);
+
+ IPackageFragment pack3 = this.sourceFolder.createPackageFragment("pack3", false, null);
+ StringBuffer typeWithSameName = new StringBuffer();
+ typeWithSameName.append("package pack3;\n");
+ typeWithSameName.append("public class TypeWithSameName {}\n");
+ pack3.createCompilationUnit("TypeWithSameName.java", typeWithSameName.toString(), false, null);
+
+ String[] order = new String[] {};
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 1, 1, false);
+ imports.addStaticImport("pack2.ContainingType", "CONSTANT", true);
+ imports.addImport("pack3.TypeWithSameName");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import static pack2.ContainingType.*;\n");
+ expected.append("\n");
+ expected.append("import pack3.TypeWithSameName;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testFloatingCommentWithBlankLine() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import com.mycompany.Bar;\n");
+ contents.append("\n");
+ contents.append("/*hello!\n");
+ contents.append("\n");
+ contents.append("this is a comment!*/\n");
+ contents.append("\n");
+ contents.append("import com.mycompany.Foo;\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] {};
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("com.mycompany.Bar");
+ imports.addImport("com.mycompany.Foo");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import com.mycompany.Bar;\n");
+ expected.append("\n");
+ expected.append("/*hello!\n");
+ expected.append("\n");
+ expected.append("this is a comment!*/\n");
+ expected.append("\n");
+ expected.append("import com.mycompany.Foo;\n");
+ expected.append("\n");
+ expected.append("public class C {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ /**
+ * Expects that an import rewrite with no effective changes produces an empty TextEdit.
+ */
+ public void testNoEdits() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("// leading comment\n");
+ contents.append("import com.mycompany.Foo;\n");
+ contents.append("// trailing comment\n");
+ contents.append("\n");
+ contents.append("// leading comment\n");
+ contents.append("import java.util.ArrayList;\n");
+ contents.append("// trailing comment\n");
+ contents.append("\n");
+ contents.append("public class C {}");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] {"com", "java"};
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("com.mycompany.Foo");
+ imports.addImport("java.util.ArrayList");
+
+ TextEdit edit = imports.rewriteImports(null);
+
+ assertEquals(0, ((MultiTextEdit) edit).getChildrenSize());
+ }
+
+ public void testAddImportWithCommentBetweenImportsAndType() throws Exception {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import com.mycompany.Bar;\n");
+ contents.append("\n");
+ contents.append("/* floating comment */\n");
+ contents.append("\n");
+ contents.append("// type comment\n");
+ contents.append("public class C {}");
+ ICompilationUnit cu = pack1.createCompilationUnit("C.java", contents.toString(), false, null);
+
+ String[] order = new String[] {"com", "java"};
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 99, 99, false);
+ imports.addImport("com.mycompany.Bar");
+ imports.addImport("com.mycompany.Foo");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import com.mycompany.Bar;\n");
+ expected.append("import com.mycompany.Foo;\n");
+ expected.append("\n");
+ expected.append("/* floating comment */\n");
+ expected.append("\n");
+ expected.append("// type comment\n");
+ expected.append("public class C {}");
+ assertEqualString(expected.toString(), cu.getSource());
+ }
+
+ public void testRenameImportedClassWithImportedNestedClass() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import com.example.A;\n");
+ contents.append("import com.example.A.ANested;\n");
+ contents.append("import com.example.C;\n");
+ contents.append("import com.example.C.CNested;\n");
+ contents.append("import com.example.E;\n");
+ contents.append("import com.example.E.ENested;\n");
+ contents.append("\n");
+ contents.append("public class Clazz {}");
+ ICompilationUnit cu = createCompilationUnit("pack1", "Clazz", contents.toString());
+
+ String[] order = new String[] { "com" };
+
+ ImportRewrite imports = newImportsRewrite(cu, order, 999, 999, true);
+ imports.setUseContextToFilterImplicitImports(true);
+ // Simulate renaming com.example.A to com.example.D.
+ imports.removeImport("com.example.A");
+ imports.removeImport("com.example.A.ANested");
+ imports.addImport("com.example.D");
+ imports.addImport("com.example.D.ANested");
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import com.example.C;\n");
+ expected.append("import com.example.C.CNested;\n");
+ expected.append("import com.example.D;\n");
+ expected.append("import com.example.D.ANested;\n");
+ expected.append("import com.example.E;\n");
+ expected.append("import com.example.E.ENested;\n");
+ expected.append("\n");
+ expected.append("public class Clazz {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testConflictsBetweenOriginalOnDemands() throws Exception {
+ // Create a type named "A" in each of two packages.
+ createCompilationUnit("conflicting1", "A");
+ createCompilationUnit("conflicting2", "A");
+
+ // Create a static member named "doStuff" in each of two types.
+ StringBuffer statics1 = new StringBuffer();
+ statics1.append("package statics;\n");
+ statics1.append("\n");
+ statics1.append("public class Statics1 {\n");
+ statics1.append(" public static void doStuff() {}\n");
+ statics1.append("}\n");
+ createCompilationUnit("statics", "Statics1", statics1.toString());
+ StringBuffer statics2 = new StringBuffer();
+ statics2.append("package statics;\n");
+ statics2.append("\n");
+ statics2.append("public class Statics2 {\n");
+ statics2.append(" public static void doStuff() {}\n");
+ statics2.append("}\n");
+ createCompilationUnit("statics", "Statics2", statics2.toString());
+
+ // Import the types and static members ambiguously via conflicting on-demand imports.
+ StringBuffer contents = new StringBuffer();
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import static statics.Statics1.*;\n");
+ contents.append("import static statics.Statics2.*;\n");
+ contents.append("\n");
+ contents.append("import conflicting1.*;\n");
+ contents.append("import conflicting2.*;\n");
+ contents.append("\n");
+ contents.append("class Clazz {}");
+ ICompilationUnit cu = createCompilationUnit("pack1", "Clazz", contents.toString());
+
+ ImportRewrite imports = newImportsRewrite(cu, new String[0], 1, 1, true);
+ imports.setUseContextToFilterImplicitImports(true);
+ // Add imports that surface the ambiguity between the existing on-demand imports.
+ imports.addImport("conflicting1.A");
+ imports.addStaticImport("statics.Statics1", "doStuff", false);
+
+ apply(imports);
+
+ StringBuffer expected = new StringBuffer();
+ // Expect that explicit single imports are added to resolve the conflicts.
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("import static statics.Statics1.*;\n");
+ expected.append("import static statics.Statics1.doStuff;\n");
+ expected.append("import static statics.Statics2.*;\n");
+ expected.append("\n");
+ expected.append("import conflicting1.*;\n");
+ expected.append("import conflicting1.A;\n");
+ expected.append("import conflicting2.*;\n");
+ expected.append("\n");
+ expected.append("class Clazz {}");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testRemoveImportsWithPackageDocComment() throws Exception {
+ StringBuffer contents = new StringBuffer();
+ contents.append("/** package doc comment */\n");
+ contents.append("package pack1;\n");
+ contents.append("\n");
+ contents.append("import com.example.Foo;\n");
+ contents.append("\n");
+ contents.append("public class Clazz {}\n");
+ ICompilationUnit cu = createCompilationUnit("pack1", "Clazz", contents.toString());
+
+ ImportRewrite rewrite = newImportsRewrite(cu, new String[] {}, 999, 999, true);
+ rewrite.setUseContextToFilterImplicitImports(true);
+ rewrite.removeImport("com.example.Foo");
+ apply(rewrite);
+
+ StringBuffer expected = new StringBuffer();
+ expected.append("/** package doc comment */\n");
+ expected.append("package pack1;\n");
+ expected.append("\n");
+ expected.append("public class Clazz {}\n");
+ assertEqualString(cu.getSource(), expected.toString());
+ }
+
+ public void testImplicitImportFiltering() throws Exception {
+ String[] order = new String[] {};
+
+ ICompilationUnit cuWithFiltering = createCompilationUnit("pack1", "CuWithFiltering");
+
+ ImportRewrite rewriteWithFiltering = newImportsRewrite(cuWithFiltering, order, 999, 999, true);
+ rewriteWithFiltering.setUseContextToFilterImplicitImports(true);
+ rewriteWithFiltering.setFilterImplicitImports(true);
+ rewriteWithFiltering.addImport("java.lang.Integer");
+ apply(rewriteWithFiltering);
+
+ StringBuffer expectedWithFiltering = new StringBuffer();
+ // Expect that the implicit java.lang import has been filtered out.
+ expectedWithFiltering.append("package pack1;\n");
+ expectedWithFiltering.append("\n");
+ expectedWithFiltering.append("public class CuWithFiltering {}");
+ assertEqualString(cuWithFiltering.getSource(), expectedWithFiltering.toString());
+
+ ICompilationUnit cuWithoutFiltering = createCompilationUnit("pack1", "CuWithoutFiltering");
+
+ ImportRewrite rewriteWithoutFiltering = newImportsRewrite(cuWithoutFiltering, order, 999, 999, true);
+ rewriteWithoutFiltering.setUseContextToFilterImplicitImports(true);
+ rewriteWithoutFiltering.setFilterImplicitImports(false);
+ rewriteWithoutFiltering.addImport("java.lang.Integer");
+ apply(rewriteWithoutFiltering);
+
+ StringBuffer expectedWithoutFiltering = new StringBuffer();
+ // Expect that the java.lang import has been added to the compilation unit.
+ expectedWithoutFiltering.append("package pack1;\n");
+ expectedWithoutFiltering.append("\n");
+ expectedWithoutFiltering.append("import java.lang.Integer;\n");
+ expectedWithoutFiltering.append("\n");
+ expectedWithoutFiltering.append("public class CuWithoutFiltering {}");
+ assertEqualString(cuWithoutFiltering.getSource(), expectedWithoutFiltering.toString());
+ }
public void testAddImports1() throws Exception {
@@ -101,9 +1416,9 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
StringBuffer buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
+ buf.append("import java.util.Map;\n");
buf.append("import java.util.Set;\n");
buf.append("import java.util.Vector;\n");
- buf.append("import java.util.Map;\n");
buf.append("\n");
buf.append("import pack.List;\n");
buf.append("import pack.List2;\n");
@@ -121,21 +1436,24 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
+ // java.net.Socket gets added to the "java" import group
+ // p.A gets added to the default match-all group at the end
+ // com.something.Foo gets added to the "com" import group
buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
buf.append("import java.net.Socket;\n");
+ buf.append("import java.util.Map;\n");
buf.append("import java.util.Set;\n");
buf.append("import java.util.Vector;\n");
- buf.append("import java.util.Map;\n");
buf.append("\n");
buf.append("import com.something.Foo;\n");
buf.append("\n");
- buf.append("import p.A;\n");
- buf.append("\n");
buf.append("import pack.List;\n");
buf.append("import pack.List2;\n");
buf.append("\n");
+ buf.append("import p.A;\n");
+ buf.append("\n");
buf.append("public class C {\n");
buf.append("}\n");
assertEqualString(cu.getSource(), buf.toString());
@@ -155,7 +1473,7 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
- String[] order= new String[] { "java.util", "java.new", "p" };
+ String[] order= new String[] { "java.util", "java.net", "p" };
ImportRewrite imports= newImportsRewrite(cu, order, 2, 2, true);
@@ -190,7 +1508,7 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append("}\n");
ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
- String[] order= new String[] { "java.util", "java.new", "p" };
+ String[] order= new String[] { "java.util", "java.net", "p" };
ImportRewrite imports= newImportsRewrite(cu, order, 2, 2, true);
@@ -332,11 +1650,13 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
+ // java.util.{Map,Set,Collections} are reduced to java.util.*
+ // java.util.Map.Entry is reduced to java.util.Map.*
buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
buf.append("import java.util.* ;\n");
- buf.append("import java.util.Map.Entry ;\n");
+ buf.append("import java.util.Map.* ;\n");
buf.append("\n");
buf.append("public class C {\n");
buf.append("}\n");
@@ -371,12 +1691,13 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
+ // With on-demand threshold set to 1, java.util.Map.Entry is reduced to java.util.Map.*.
buf= new StringBuffer();
buf.append(
"package pack1;\n" +
"\n" +
"import java.util.*;\n" +
- "import java.util.Map.Entry;\n" +
+ "import java.util.Map.*;\n" +
"\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
@@ -551,8 +1872,7 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
- buf.append("import java.util.Vector;\n");
- buf.append("import java.util.Map;\n");
+ buf.append("import java.util.*;\n");
buf.append("\n");
buf.append("import pack.List2;\n");
buf.append("\n");
@@ -644,7 +1964,6 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
}
public void testAddImports_bug23078() throws Exception {
-
IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
StringBuffer buf= new StringBuffer();
buf.append("package pack1;\n");
@@ -657,16 +1976,58 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
String[] order= new String[] { };
- ImportRewrite imports= newImportsRewrite(cu, order, 2, 2, true);
+ ImportRewrite imports= newImportsRewrite(cu, order, 3, 3, true);
+ imports.addImport("p.A");
imports.addImport("p.Inner");
+ imports.addImport("p.Inner.*");
apply(imports);
+ // Without having set useContextToFilterImplicitImports to true, we get pre-3.6 behavior,
+ // which sorts imports by containing type and/or package before sorting by qualified name.
buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
+ buf.append("import p.A;\n");
buf.append("import p.Inner;\n");
buf.append("import p.A.*;\n");
+ buf.append("import p.Inner.*;\n");
+ buf.append("\n");
+ buf.append("public class C {\n");
+ buf.append("}\n");
+ assertEqualString(cu.getSource(), buf.toString());
+ }
+
+ public void testAddImports_bug23078_usingContext() throws Exception {
+ IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("package pack1;\n");
+ buf.append("\n");
+ buf.append("import p.A.*;\n");
+ buf.append("\n");
+ buf.append("public class C {\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
+
+ String[] order= new String[] { };
+
+ ImportRewrite imports= newImportsRewrite(cu, order, 3, 3, true);
+ imports.setUseContextToFilterImplicitImports(true);
+ imports.addImport("p.A");
+ imports.addImport("p.Inner");
+ imports.addImport("p.Inner.*");
+
+ apply(imports);
+
+ // Having set useContextToFilterImplicitImports to true, we get 3.6-and-later behavior,
+ // which sorts imports by containing package and then by qualified name.
+ buf= new StringBuffer();
+ buf.append("package pack1;\n");
+ buf.append("\n");
+ buf.append("import p.A;\n");
+ buf.append("import p.A.*;\n");
+ buf.append("import p.Inner;\n");
+ buf.append("import p.Inner.*;\n");
buf.append("\n");
buf.append("public class C {\n");
buf.append("}\n");
@@ -737,12 +2098,14 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
assertEqualString(cu.getSource(), buf.toString());
}
+ /**
+ * Expects that, in the absence of a package declaration, comments preceding the first import
+ * declaration are treated as file header comments and left in place.
+ */
public void testAddImports_bug121428() throws Exception {
IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
StringBuffer buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
buf.append("/** comment */\n");
buf.append("import java.lang.System;\n");
buf.append("\n");
@@ -758,8 +2121,6 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
buf.append("/** comment */\n");
buf.append("import java.io.Exception;\n");
buf.append("\n");
@@ -1240,7 +2601,8 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
public void testPackageInfo() throws Exception {
IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
StringBuffer buf= new StringBuffer();
- buf.append("\npackage pack1;");
+ buf.append("\n");
+ buf.append("package pack1;");
ICompilationUnit cu= pack1.createCompilationUnit("package-info.java", buf.toString(), false, null);
@@ -1252,7 +2614,9 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
buf= new StringBuffer();
- buf.append("\npackage pack1;\n");
+ buf.append("\n");
+ buf.append("package pack1;\n");
+ buf.append("\n");
buf.append("import foo.Bar;\n");
assertEqualString(cu.getSource(), buf.toString());
}
@@ -1315,87 +2679,25 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
assertEqualString(units[2].getSource(), buf.toString());
}
+ /**
+ * Expects that comments in a variety of positions around and between import declarations
+ * are preserved when restoreExistingImports is set to false.
+ */
public void testAddImports_bug24804() throws Exception {
-
IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
StringBuffer buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
- buf.append("import java.lang.String;\n");
- buf.append("/** comment */\n");
- buf.append("import java.lang.System;\n");
+ buf.append("/** floating comment before first import */\n");
buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
-
- String[] order= new String[] { "java" };
-
- ImportRewrite imports= newImportsRewrite(cu, order, 99, 99, false);
- imports.addImport("java.io.Exception");
-
- apply(imports);
-
- buf= new StringBuffer();
- buf.append("package pack1;\n");
+ buf.append("import java.util.ArrayList; // trailing same-line comment\n");
buf.append("\n");
- buf.append("import java.io.Exception;\n");
- buf.append("/** comment */\n");
- buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- assertEqualString(cu.getSource(), buf.toString());
- }
-
- public void testAddImports_bug24804_2() throws Exception {
-
- IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
- StringBuffer buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
- buf.append("import java.lang.AssertionError;//test\n");
- buf.append("\n");
- buf.append("/** comment2 */\n");
- buf.append("\n");
- buf.append("/** comment */\n");
- buf.append("import java.lang.System;\n");
- buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
-
- String[] order= new String[] { "java" };
-
- ImportRewrite imports= newImportsRewrite(cu, order, 99, 99, true);
- imports.addImport("java.io.Exception");
-
- apply(imports);
-
- buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
- buf.append("import java.io.Exception;\n");
- buf.append("import java.lang.AssertionError;//test\n");
- buf.append("\n");
- buf.append("/** comment2 */\n");
- buf.append("\n");
- buf.append("/** comment */\n");
- buf.append("import java.lang.System;\n");
- buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- assertEqualString(cu.getSource(), buf.toString());
- }
-
- public void testAddImports_bug24804_3() throws Exception {
-
- IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
- StringBuffer buf= new StringBuffer();
- buf.append("package pack1;\n");
+ buf.append("/** floating comment between imports*/\n");
buf.append("\n");
- buf.append("import java.lang.String;//test\n");
- buf.append("/** comment */\n");
- buf.append("import java.lang.System;\n");
+ buf.append("/** preceding-line comment */\n");
+ buf.append("import java.util.Collection;\n");
+ buf.append("/** comment on line between imports */\n");
+ buf.append("import java.util.Deque;\n");
buf.append("\n");
buf.append("public class C {\n");
buf.append("}\n");
@@ -1404,97 +2706,31 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
String[] order= new String[] { "java" };
ImportRewrite imports= newImportsRewrite(cu, order, 99, 99, false);
- imports.addImport("java.io.Exception");
+ imports.addImport("java.util.ArrayList");
+ imports.addImport("java.util.Collection");
+ imports.addImport("java.util.Deque");
apply(imports);
buf= new StringBuffer();
buf.append("package pack1;\n");
buf.append("\n");
- buf.append("import java.io.Exception;\n");
- buf.append("//test\n");
- buf.append("/** comment */\n");
+ buf.append("/** floating comment before first import */\n");
buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- assertEqualString(cu.getSource(), buf.toString());
- }
-
- public void testAddImports_bug24804_4() throws Exception {
-
- IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
- StringBuffer buf= new StringBuffer();
- buf.append("package pack1;\n");
+ buf.append("import java.util.ArrayList; // trailing same-line comment\n");
buf.append("\n");
- buf.append("import java.lang.AssertionError;//test\n");
+ buf.append("/** floating comment between imports*/\n");
buf.append("\n");
- buf.append("/** comment2 */\n");
- buf.append("\n");
- buf.append("/** comment */\n");
- buf.append("import java.lang.System; /** comment3 */\n");
- buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
-
- String[] order= new String[] { "java" };
-
- ImportRewrite imports= newImportsRewrite(cu, order, 99, 99, false);
- imports.addImport("java.io.Exception");
-
- apply(imports);
-
- buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
- buf.append("import java.io.Exception;\n");
- buf.append("//test\n");
- buf.append("/** comment2 */\n");
- buf.append("/** comment */\n");
- buf.append("/** comment3 */\n");
+ buf.append("/** preceding-line comment */\n");
+ buf.append("import java.util.Collection;\n");
+ buf.append("/** comment on line between imports */\n");
+ buf.append("import java.util.Deque;\n");
buf.append("\n");
buf.append("public class C {\n");
buf.append("}\n");
assertEqualString(cu.getSource(), buf.toString());
}
- public void testAddImports_bug24804_5() throws Exception {
-
- IPackageFragment pack1= this.sourceFolder.createPackageFragment("pack1", false, null);
- StringBuffer buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
- buf.append("import java.lang.AssertionError; //test\n");
- buf.append("\n");
- buf.append("/** comment2 */\n");
- buf.append("\n");
- buf.append("/** comment */\n");
- buf.append("import java.lang.System;\n");
- buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- ICompilationUnit cu= pack1.createCompilationUnit("C.java", buf.toString(), false, null);
-
- String[] order= new String[] { "java" };
-
- ImportRewrite imports= newImportsRewrite(cu, order, 1, 1, false);
- imports.addImport("java.io.Exception");
-
- apply(imports);
-
- buf= new StringBuffer();
- buf.append("package pack1;\n");
- buf.append("\n");
- buf.append("import java.io.*;\n");
- buf.append("//test\n");
- buf.append("/** comment2 */\n");
- buf.append("/** comment */\n");
- buf.append("\n");
- buf.append("public class C {\n");
- buf.append("}\n");
- assertEqualString(cu.getSource(), buf.toString());
- }
-
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=376930
public void testBug376930() throws Exception {
IPackageFragment pack1 = this.sourceFolder.createPackageFragment("pack1", false, null);
@@ -1595,9 +2831,9 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
"import java.io.*;\n" +
"\n" +
+ "// comment 1\n" +
"import java.util.*; // test\n" +
"import java.util.Map.Entry; // test2\n" +
"import java.util.Map.SomethingElse;\n" +
@@ -1657,10 +2893,10 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/* lead 1*/ import java.io.*;\n" +
+ "import java.io.*;\n" +
"\n" +
- "import java.util.*; // test1\n" +
+ "// comment 1\n" +
+ "/* lead 1*/ import java.util.*; // test1\n" +
"/* lead 2*/import java.util.Map.Entry; // test2\n" +
"/* lead 3*/ import java.util.Map.SomethingElse; // test3\n" +
"// commen 3\n" +
@@ -1718,16 +2954,8 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/* lead 1*/ " +
"import java.io.*;\n" +
- "// test1\n" +
- "/* lead 2*/\n" +
- "// test2\n" +
- "/* lead 3*/ \n" +
- "// test3\n" +
- "// commen 3\n" +
- "\n" +
+ "\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
" HashMap h;\n" +
@@ -1783,10 +3011,10 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/* lead 1*/ import java.io.*;\n" +
+ "import java.io.*;\n" +
"\n" +
- "import java.util.*; // test1\n" +
+ "// comment 1\n" +
+ "/* lead 1*/ import java.util.*; // test1\n" +
"/* lead 2*/import java.util.Map.*; // test2\n" +
"/* lead 3*/ import java.util.Map.SomethingElse; // test3\n" +
"// commen 3\n" +
@@ -1844,16 +3072,8 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/* lead 1*/ " +
"import java.io.*;\n" +
- "// test1\n" +
- "/* lead 2*/\n" +
- "// test2\n" +
- "/* lead 3*/ \n" +
- "// test3\n" +
- "// commen 3\n" +
- "\n" +
+ "\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
" HashMap h;\n" +
@@ -1906,18 +3126,20 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
buf = new StringBuffer();
+ // java.util.Map.* is placed after java.util.* and is assigned the comments
+ // from java.util.Map.SomethingElse.
buf.append(
"package pack1;\n" +
"\n" +
"// comment 1\n" +
"/* lead 1*/ import java.util.*; // test1\n" +
+ "/* lead 3*/\n" +
+ "// test3\n" +
+ "// commen 3\n" +
"import java.util.Map.*;\n" +
"\n" +
"/* lead 2*/import java.io.PrintWriter.*; // test2\n" +
"\n" +
- "/* lead 3*/ import java.util.Map.SomethingElse; // test3\n" +
- "// commen 3\n" +
- "\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
" HashMap h;\n" +
@@ -1969,17 +3191,19 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
+ // java.util.Map.* takes the place of java.util.Map.SomethingElse,
+ // and the latter's comments are reassigned to it.
buf = new StringBuffer();
buf.append(
"package pack1;\n" +
"\n" +
"// comment 1\n" +
"/* lead 2*/import java.io.PrintWriter.*; // test2\n" +
- "\n" +
"/* lead 1*/ import java.util.*; // test1\n" +
- "import java.util.Map.*;\n" +
- "/* lead 3*/ import java.util.Map.SomethingElse; // test3\n" +
+ "/* lead 3*/\n" +
+ "// test3\n" +
"// commen 3\n" +
+ "import java.util.Map.*;\n" +
"\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
@@ -2034,13 +3258,8 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/* lead 2*//* lead 1*/ import java.util.*; // test1\n" +
- "// test2\n" +
- "/* lead 3*/ \n" +
- "// test3\n" +
- "// commen 3\n" +
- "\n" +
+ "/* lead 1*/ import java.util.*; // test1\n" +
+ "\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
" HashMap h;\n" +
@@ -2090,19 +3309,17 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
apply(imports);
+ // java.util.Map.* takes the place of java.util.Map.SomethingElse,
+ // and the latter's comments are reassigned to it.
buf = new StringBuffer();
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/* lead 1*/ " +
- "import java.util.Map.*; // test1\n" +
- "/* lead 2*/\n" +
- "// test2\n" +
- "/* lead 3*/ \n" +
+ "/* lead 3*/\n" +
"// test3\n" +
"// commen 3\n" +
- "\n" +
+ "import java.util.Map.*;\n" +
+ "\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
" HashMap h;\n" +
@@ -2158,9 +3375,10 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"// comment 1\n" +
"/* lead 2*/import java.io.PrintWriter.*; // test2\n" +
"\n" +
- "/* lead 1*/ import java.util.*;\n" +
- " // test1\n" +
+ "/* lead 1*/\n" +
+ "// test1\n" +
"// commen 3\n" +
+ "import java.util.*;\n" +
"\n" +
"public class C {\n" +
" public static void main(String[] args) {\n" +
@@ -2347,17 +3565,20 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" * don't move me 1\n" +
" *\n" +
" */\n" +
- "import java.awt.*;// test1\n" +
+ "// test1\n" +
+ "import java.awt.*;\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
- "import java.io.*;// test2\n" +
+ "// test2\n" +
+ "import java.io.*;\n" +
"\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
- "import java.util.*;// test3\n" +
+ "// test3\n" +
"// commen 3\n" +
+ "import java.util.*;\n" +
"\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
@@ -2435,20 +3656,24 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"// lead 1\n" +
"import java.awt.List;// test1\n" +
"\n" +
- "//lead 3\n" +
- "import java.util.HashMap;// test3\n" +
- "// commen 3\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
"import java.io.Serializable;// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
+ "\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
+ "\n" +
+ "//lead 3\n" +
+ "import java.util.HashMap;// test3\n" +
+ "// commen 3\n" +
+ "\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
" List l = new List();\n" +
@@ -2524,17 +3749,21 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"\n" +
"// lead 1\n" +
"import java.awt.List;// test1\n" +
+ "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
"import java.io.Serializable;// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
+ "\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
+ "\n" +
"//lead 3\n" +
"import java.util.HashMap;// test3\n" +
"// commen 3\n" +
@@ -2613,21 +3842,28 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" */\n" +
"\n" +
"// lead 1\n" +
- "import java.awt.*;// test1\n" +
+ "// test1\n" +
+ "import java.awt.*;\n" +
+ "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
- "import java.io.*;// test2\n" +
+ "// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
+ "import java.io.*;\n" +
+ "\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
+ "\n" +
"//lead 3\n" +
- "import java.util.*;// test3\n" +
+ "// test3\n" +
"// commen 3\n" +
+ "import java.util.*;\n" +
"\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
@@ -2701,23 +3937,18 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" */\n" +
"\n" +
"// lead 1\n" +
- "import java.awt.*;// test1\n" +
+ "// test1\n" +
+ "import java.awt.*;\n" +
"\n" +
- "//lead 3\n" +
- "import java.util.*;// test3\n" +
- "// commen 3\n" +
"/*\n" +
" * don't move me 4\n" +
- " */" +
- "/*\n" +
- " * don't move me 2\n" +
- " */\n" +
- "// lead 2\n" +
- "// test2\n" +
- "/*\n" +
- " * don't move me 3\n" +
" */\n" +
"\n" +
+ "//lead 3\n" +
+ "// test3\n" +
+ "// commen 3\n" +
+ "import java.util.*;\n" +
+ "\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
" List l = new List();\n" +
@@ -2793,18 +4024,22 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"\n" +
"// lead 1\n" +
"import java.awt.List;// test1\n" +
+ "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
- "import java.io.*;\n" +
"// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
+ "import java.io.*;\n" +
+ "\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
+ "\n" +
"//lead 3\n" +
"import java.util.HashMap;// test3\n" +
"// commen 3\n" +
@@ -2886,21 +4121,14 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"import java.awt.List;// test1\n" +
"import java.io.PrintWriter;\n" +
"\n" +
- "//lead 3\n" +
- "import java.util.HashMap;// test3\n" +
- "// commen 3\n" +
"/*\n" +
" * don't move me 4\n" +
- " */" +
- "/*\n" +
- " * don't move me 2\n" +
" */\n" +
- "// lead 2\n" +
- "// test2\n" +
- "/*\n" +
- " * don't move me 3\n" +
- " */\n" +
- "\n" +
+ "\n" +
+ "//lead 3\n" +
+ "import java.util.HashMap;// test3\n" +
+ "// commen 3\n" +
+ "\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
" List l = new List();\n" +
@@ -2976,18 +4204,22 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"\n" +
"// lead 1\n" +
"import java.awt.List;// test1\n" +
+ "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
- "import java.io.*;\n" +
"// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
+ "import java.io.*;\n" +
+ "\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
+ "\n" +
"//lead 3\n" +
"import java.util.HashMap;// test3\n" +
"// commen 3\n" +
@@ -3074,25 +4306,30 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
"// lead 1\n" +
"import java.awt.List;// test1\n" +
"\n" +
- "//lead 4\n" +
- "import java.util.HashMap;// test4\n" +
- "// commen 3\n" +
"/*\n" +
" * keep me with Serializable\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
- "// lead 3\n" +
- "import java.io.*;// test3\n" +
- "/*\n" +
- " * keep me with PrintWriter\n" +
- " */\n" +
"// test2\n" +
"/*\n" +
" * keep me with Serializable 2\n" +
" */\n" +
+ "// lead 3\n" +
+ "// test3\n" +
+ "/*\n" +
+ " * keep me with PrintWriter\n" +
+ " */\n" +
+ "import java.io.*;\n" +
+ "\n" +
"/*\n" +
" * don't move me\n" +
" */\n" +
+ "\n" +
+ "//lead 4\n" +
+ "import java.util.HashMap;// test4\n" +
+ "// commen 3\n" +
+ "\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
" List l = new List();\n" +
@@ -3165,23 +4402,28 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" */\n" +
"\n" +
"// lead 1\n" +
- "import java.awt.*;// test1\n" +
+ "// test1\n" +
+ "import java.awt.*;\n" +
"\n" +
"//lead 3\n" +
- "import java.util.*;// test3\n" +
+ "// test3\n" +
"// commen 3\n" +
+ "import java.util.*;\n" +
+ "\n" +
+ "/*\n" +
+ " * don't move me 2\n" +
+ " */\n" +
"\n" +
"// lead 2\n" +
- "import java.io.*;// test2\n" +
+ "// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
- "/*\n" +
- " * don't move me 2\n" +
- " */\n" +
+ "import java.io.*;\n" +
+ "\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
" List l = new List();\n" +
@@ -3246,14 +4488,6 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/*\n" +
- " * don't move me 1\n" +
- " *\n" +
- " */\n" +
- "\n" +
- "// lead 1\n" +
- "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
@@ -3268,7 +4502,6 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" * don't move me 4\n" +
" */\n" +
"\n" +
- "\n" +
"//lead 3\n" +
"import java.util.HashMap;// test3\n" +
"// commen 3\n" +
@@ -3336,14 +4569,6 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
buf.append(
"package pack1;\n" +
"\n" +
- "// comment 1\n" +
- "/*\n" +
- " * don't move me 1\n" +
- " *\n" +
- " */\n" +
- "// lead 1\n" +
- "/* i am with List */\n" +
- "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
@@ -3358,7 +4583,6 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" * don't move me 4\n" +
" */\n" +
"\n" +
- "\n" +
"//lead 3\n" +
"import java.util.HashMap;// test3\n" +
"// commen 3\n" +
@@ -3437,23 +4661,30 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" *\n" +
" */\n" +
"// lead 1\n" +
- "import java.awt.List;// test1\n" +
+ "// test1\n" +
"/* i am with List */\n" +
+ "import java.awt.List;\n" +
+ "\n" +
"/*\n" +
" * don't move me 2\n" +
" */\n" +
+ "\n" +
"// lead 2\n" +
- "import java.io.PrintWriter;// test2\n" +
+ "// test2\n" +
"/*\n" +
" * don't move me 3\n" +
" */\n" +
+ "import java.io.PrintWriter;\n" +
"import java.io.Serializable;\n" +
+ "\n" +
"/*\n" +
" * don't move me 4\n" +
" */\n" +
+ "\n" +
"//lead 3\n" +
- "import java.util.HashMap;// test3\n" +
+ "// test3\n" +
"// commen 3\n" +
+ "import java.util.HashMap;\n" +
"import java.util.Map;\n" +
"\n" +
"public class C implements Serializable{\n" +
@@ -3533,26 +4764,33 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
" *\n" +
" */\n" +
"// lead 1\n" +
- "import java.awt.*;// test1\n" +
+ "// test1\n" +
"/* i am with List */\n" +
+ "import java.awt.*;\n" +
"\n" +
- "//lead 3\n" +
- "import java.util.*;// test3\n" +
"/*\n" +
- " * don't move me 3\n" +
+ " * don't move me 4\n" +
" */\n" +
- "/*keep me with Map.Entry*/\n" +
- "import java.util.Map.Entry;// member type import\n" +
- "/*keep me with Map.Entry 2*/\n" +
+ "\n" +
+ "// lead 2\n" +
+ "// test2\n" +
+ "// commen 3\n" +
+ "import java.io.*;\n" +
+ "\n" +
"/*\n" +
" * don't move me 2\n" +
- " */" +
+ " */\n" +
+ "\n" +
+ "//lead 3\n" +
+ "// test3\n" +
"/*\n" +
- " * don't move me 4\n" +
+ " * don't move me 3\n" +
" */\n" +
- "// lead 2\n" +
- "import java.io.*;// test2\n" +
- "// commen 3\n" +
+ "import java.util.*;\n" +
+ "/*keep me with Map.Entry*/\n" +
+ "// member type import\n" +
+ "/*keep me with Map.Entry 2*/\n" +
+ "import java.util.Map.*;\n" +
"\n" +
"public class C implements Serializable{\n" +
" public static void main(String[] args) {\n" +
@@ -3613,6 +4851,21 @@ public class ImportRewriteTest extends AbstractJavaModelTests {
assertEquals("pack1.X", actualType.toString());
}
+ private ICompilationUnit createCompilationUnit(String packageName, String className) throws JavaModelException {
+ StringBuffer contents = new StringBuffer();
+ contents.append("package " + packageName + ";\n");
+ contents.append("\n");
+ contents.append("public class " + className + " {}");
+ return createCompilationUnit(packageName, className, contents.toString());
+ }
+
+ private ICompilationUnit createCompilationUnit(
+ String packageName, String className, String contents) throws JavaModelException {
+ IPackageFragment pack1 = this.sourceFolder.createPackageFragment(packageName, false, null);
+ ICompilationUnit cu = pack1.createCompilationUnit(className + ".java", contents, /* force */ true, null);
+ return cu;
+ }
+
private void assertAddedAndRemoved(ImportRewrite imports, String[] expectedAdded, String[] expectedRemoved, String[] expectedAddedStatic, String[] expectedRemovedStatic) {
assertEqualStringsIgnoreOrder(imports.getAddedImports(), expectedAdded);
assertEqualStringsIgnoreOrder(imports.getAddedStaticImports(), expectedAddedStatic);
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java
index 9043aacf87..110a27e4a9 100644
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.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,14 +7,18 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
*******************************************************************************/
package org.eclipse.jdt.core.dom.rewrite;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -62,7 +66,10 @@ import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.WildcardType;
-import org.eclipse.jdt.internal.core.dom.rewrite.ImportRewriteAnalyzer;
+import org.eclipse.jdt.internal.core.dom.rewrite.imports.ImportRewriteConfiguration;
+import org.eclipse.jdt.internal.core.dom.rewrite.imports.ImportRewriteAnalyzer;
+import org.eclipse.jdt.internal.core.dom.rewrite.imports.ImportRewriteConfiguration.ImplicitImportIdentification;
+import org.eclipse.jdt.internal.core.dom.rewrite.imports.ImportRewriteConfiguration.ImportContainerSorting;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.text.edits.MultiTextEdit;
@@ -122,6 +129,14 @@ public final class ImportRewrite {
public final static int RES_NAME_CONFLICT= 3;
/**
+ * Result constant signaling that the given element must be imported explicitly (and must not be folded into
+ * an on-demand import or filtered as an implicit import).
+ *
+ * @since 3.11
+ */
+ public final static int RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT= 4;
+
+ /**
* Kind constant specifying that the element is a type import.
*/
public final static int KIND_TYPE= 1;
@@ -138,14 +153,17 @@ public final class ImportRewrite {
/**
* Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
- * unknown ({@link #RES_NAME_UNKNOWN}) or if its name conflicts ({@link #RES_NAME_CONFLICT}) with an other element.
+ * unknown ({@link #RES_NAME_UNKNOWN}), unknown in the context but known to require an explicit import
+ * ({@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}), or if its name conflicts ({@link #RES_NAME_CONFLICT})
+ * with an other element.
+ *
* @param qualifier The qualifier of the element, can be package or the qualified name of a type
* @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
* @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
* {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
* {@link #RES_NAME_UNKNOWN} by default.
- * @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN} or
- * {@link #RES_NAME_CONFLICT}.
+ * @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN},
+ * {@link #RES_NAME_CONFLICT}, or {@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}.
*/
public abstract int findInContext(String qualifier, String name, int kind);
}
@@ -166,8 +184,20 @@ public final class ImportRewrite {
private int importOnDemandThreshold;
private int staticImportOnDemandThreshold;
- private List addedImports;
- private List removedImports;
+ private List<String> addedImports;
+ private List<String> removedImports;
+
+ /**
+ * Simple names of non-static imports which must not be reduced into on-demand imports
+ * or filtered out as implicit.
+ */
+ private Set<String> typeExplicitSimpleNames;
+
+ /**
+ * Simple names of static imports which must not be reduced into on-demand imports
+ * or filtered out as implicit.
+ */
+ private Set<String> staticExplicitSimpleNames;
private String[] createdImports;
private String[] createdStaticImports;
@@ -266,8 +296,10 @@ public final class ImportRewrite {
return findInImports(qualifier, name, kind);
}
};
- this.addedImports= null; // Initialized on use
- this.removedImports= null; // Initialized on use
+ this.addedImports= new ArrayList<String>();
+ this.removedImports= new ArrayList<String>();
+ this.typeExplicitSimpleNames = new HashSet<String>();
+ this.staticExplicitSimpleNames = new HashSet<String>();
this.createdImports= null;
this.createdStaticImports= null;
@@ -984,6 +1016,10 @@ public final class ImportRewrite {
if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
addEntry(STATIC_PREFIX + key);
}
+ if (res == ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT) {
+ addEntry(STATIC_PREFIX + key);
+ this.staticExplicitSimpleNames.add(simpleName);
+ }
return simpleName;
}
@@ -1012,35 +1048,31 @@ public final class ImportRewrite {
if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
addEntry(NORMAL_PREFIX + fullTypeName);
}
+ if (res == ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT) {
+ addEntry(NORMAL_PREFIX + fullTypeName);
+ this.typeExplicitSimpleNames.add(typeName);
+ }
return typeName;
}
private void addEntry(String entry) {
this.existingImports.add(entry);
- if (this.removedImports != null) {
- if (this.removedImports.remove(entry)) {
- return;
- }
+ if (this.removedImports.remove(entry)) {
+ return;
}
- if (this.addedImports == null) {
- this.addedImports= new ArrayList();
- }
this.addedImports.add(entry);
}
private boolean removeEntry(String entry) {
if (this.existingImports.remove(entry)) {
- if (this.addedImports != null) {
- if (this.addedImports.remove(entry)) {
- return true;
- }
- }
- if (this.removedImports == null) {
- this.removedImports= new ArrayList();
+ if (this.addedImports.remove(entry)) {
+ return true;
}
+
this.removedImports.add(entry);
+
return true;
}
return false;
@@ -1118,40 +1150,66 @@ public final class ImportRewrite {
usedAstRoot= (CompilationUnit) parser.createAST(new SubProgressMonitor(monitor, 1));
}
+ ImportRewriteConfiguration config= buildImportRewriteConfiguration();
+
ImportRewriteAnalyzer computer=
- new ImportRewriteAnalyzer(
- this.compilationUnit,
- usedAstRoot,
- this.importOrder,
- this.importOnDemandThreshold,
- this.staticImportOnDemandThreshold,
- this.restoreExistingImports,
- this.useContextToFilterImplicitImports);
- computer.setFilterImplicitImports(this.filterImplicitImports);
-
- if (this.addedImports != null) {
- for (int i= 0; i < this.addedImports.size(); i++) {
- String curr= (String) this.addedImports.get(i);
- computer.addImport(curr.substring(1), STATIC_PREFIX == curr.charAt(0), usedAstRoot, this.restoreExistingImports);
- }
+ new ImportRewriteAnalyzer(this.compilationUnit, usedAstRoot, config);
+
+ for (String addedImport : this.addedImports) {
+ boolean isStatic = STATIC_PREFIX == addedImport.charAt(0);
+ String qualifiedName = addedImport.substring(1);
+ computer.addImport(isStatic, qualifiedName);
}
- if (this.removedImports != null) {
- for (int i= 0; i < this.removedImports.size(); i++) {
- String curr= (String) this.removedImports.get(i);
- computer.removeImport(curr.substring(1), STATIC_PREFIX == curr.charAt(0));
- }
+ for (String removedImport : this.removedImports) {
+ boolean isStatic = STATIC_PREFIX == removedImport.charAt(0);
+ String qualifiedName = removedImport.substring(1);
+ computer.removeImport(isStatic, qualifiedName);
}
- TextEdit result= computer.getResultingEdits(new SubProgressMonitor(monitor, 1));
- this.createdImports= computer.getCreatedImports();
- this.createdStaticImports= computer.getCreatedStaticImports();
- return result;
+ for (String typeExplicitSimpleName : this.typeExplicitSimpleNames) {
+ computer.requireExplicitImport(false, typeExplicitSimpleName);
+ }
+
+ for (String staticExplicitSimpleName : this.staticExplicitSimpleNames) {
+ computer.requireExplicitImport(true, staticExplicitSimpleName);
+ }
+
+ ImportRewriteAnalyzer.RewriteResult result= computer.analyzeRewrite(new SubProgressMonitor(monitor, 1));
+
+ this.createdImports= result.getCreatedImports();
+ this.createdStaticImports= result.getCreatedStaticImports();
+
+ return result.getTextEdit();
} finally {
monitor.done();
}
}
+ private ImportRewriteConfiguration buildImportRewriteConfiguration() {
+ ImportRewriteConfiguration.Builder configBuilder;
+
+ if (this.restoreExistingImports) {
+ configBuilder= ImportRewriteConfiguration.Builder.preservingOriginalImports();
+ } else {
+ configBuilder= ImportRewriteConfiguration.Builder.discardingOriginalImports();
+ }
+
+ configBuilder.setImportOrder(Arrays.asList(this.importOrder));
+ configBuilder.setTypeOnDemandThreshold(this.importOnDemandThreshold);
+ configBuilder.setStaticOnDemandThreshold(this.staticImportOnDemandThreshold);
+
+ configBuilder.setTypeContainerSorting(this.useContextToFilterImplicitImports ?
+ ImportContainerSorting.BY_PACKAGE : ImportContainerSorting.BY_PACKAGE_AND_CONTAINING_TYPE);
+
+ configBuilder.setStaticContainerSorting(ImportContainerSorting.BY_PACKAGE_AND_CONTAINING_TYPE);
+
+ configBuilder.setImplicitImportIdentification(this.filterImplicitImports ?
+ ImplicitImportIdentification.JAVA_LANG_AND_CU_PACKAGE : ImplicitImportIdentification.NONE);
+
+ return configBuilder.build();
+ }
+
/**
* Returns all new non-static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
* or <code>null</code> if these methods have not been called yet.
@@ -1219,24 +1277,23 @@ public final class ImportRewrite {
* @return boolean returns if any changes to imports have been recorded.
*/
public boolean hasRecordedChanges() {
- return !this.restoreExistingImports ||
- (this.addedImports != null && !this.addedImports.isEmpty()) ||
- (this.removedImports != null && !this.removedImports.isEmpty());
+ return !this.restoreExistingImports
+ || !this.addedImports.isEmpty()
+ || !this.removedImports.isEmpty();
}
- private static String[] filterFromList(List imports, char prefix) {
+ private static String[] filterFromList(List<String> imports, char prefix) {
if (imports == null) {
return CharOperation.NO_STRINGS;
}
- ArrayList res= new ArrayList();
- for (int i= 0; i < imports.size(); i++) {
- String curr= (String) imports.get(i);
+ List<String> res= new ArrayList<String>();
+ for (String curr : imports) {
if (prefix == curr.charAt(0)) {
res.add(curr.substring(1));
}
}
- return (String[]) res.toArray(new String[res.size()]);
+ return res.toArray(new String[res.size()]);
}
private void annotateList(List annotations, IAnnotationBinding [] annotationBindings, AST ast, ImportRewriteContext context) {
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ImportRewriteAnalyzer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ImportRewriteAnalyzer.java
deleted file mode 100644
index c83c7f26d4..0000000000
--- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ImportRewriteAnalyzer.java
+++ /dev/null
@@ -1,1522 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2000, 2013 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
- * Stephan Herrmann - Contribution for Bug 378024 - Ordering of comments between imports not preserved
- *******************************************************************************/
-package org.eclipse.jdt.internal.core.dom.rewrite;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.jdt.core.IBuffer;
-import org.eclipse.jdt.core.ICompilationUnit;
-import org.eclipse.jdt.core.IJavaElement;
-import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.core.Signature;
-import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.dom.ASTNode;
-import org.eclipse.jdt.core.dom.Comment;
-import org.eclipse.jdt.core.dom.CompilationUnit;
-import org.eclipse.jdt.core.dom.ImportDeclaration;
-import org.eclipse.jdt.core.dom.LineComment;
-import org.eclipse.jdt.core.dom.PackageDeclaration;
-import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
-import org.eclipse.jdt.core.search.IJavaSearchConstants;
-import org.eclipse.jdt.core.search.IJavaSearchScope;
-import org.eclipse.jdt.core.search.SearchEngine;
-import org.eclipse.jdt.core.search.TypeNameRequestor;
-import org.eclipse.jdt.internal.core.JavaProject;
-import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.Region;
-import org.eclipse.text.edits.DeleteEdit;
-import org.eclipse.text.edits.InsertEdit;
-import org.eclipse.text.edits.MultiTextEdit;
-
-@SuppressWarnings({ "rawtypes", "unchecked" })
-public final class ImportRewriteAnalyzer {
-
- private final ICompilationUnit compilationUnit;
- private final ArrayList packageEntries;
-
- private final List importsCreated;
- private final List staticImportsCreated;
-
- private final IRegion replaceRange;
-
- private final int importOnDemandThreshold;
- private final int staticImportOnDemandThreshold;
-
- private boolean filterImplicitImports;
- private boolean useContextToFilterImplicitImports;
- private boolean findAmbiguousImports;
-
- private IRegion[] preserveExistingCommentsRanges;
-
- private int flags= 0;
-
- private static final int F_NEEDS_LEADING_DELIM= 2;
- private static final int F_NEEDS_TRAILING_DELIM= 4;
-
- private static final String JAVA_LANG= "java.lang"; //$NON-NLS-1$
-
- public ImportRewriteAnalyzer(
- ICompilationUnit cu,
- CompilationUnit root,
- String[] importOrder,
- int threshold,
- int staticThreshold,
- boolean restoreExistingImports,
- boolean useContextToFilterImplicitImports) {
- this.compilationUnit= cu;
- this.importOnDemandThreshold= threshold;
- this.staticImportOnDemandThreshold= staticThreshold;
- this.useContextToFilterImplicitImports = useContextToFilterImplicitImports;
-
- this.filterImplicitImports= true;
- this.findAmbiguousImports= true; //!restoreExistingImports;
-
- this.packageEntries= new ArrayList(20);
- this.importsCreated= new ArrayList();
- this.staticImportsCreated= new ArrayList();
- this.flags= 0;
-
- this.replaceRange= evaluateReplaceRange(root);
- if (restoreExistingImports) {
- addExistingImports(root);
- } else {
- // collect all existing comments inside imports and concatenate them
- this.preserveExistingCommentsRanges = retrieveExistingCommentsInImports(root);
- }
-
- PackageEntry[] order= new PackageEntry[importOrder.length];
- for (int i= 0; i < order.length; i++) {
- String curr= importOrder[i];
- if (curr.length() > 0 && curr.charAt(0) == '#') {
- curr= curr.substring(1);
- order[i]= new PackageEntry(curr, curr, true); // static import group
- } else {
- order[i]= new PackageEntry(curr, curr, false); // normal import group
- }
- }
-
- addPreferenceOrderHolders(order);
- }
-
- private int getSpacesBetweenImportGroups() {
- try {
- int num= Integer.parseInt(this.compilationUnit.getJavaProject().getOption(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS, true));
- if (num >= 0)
- return num;
- } catch (NumberFormatException e) {
- // fall through
- }
- return 1;
- }
-
- private boolean insertSpaceBeforeSemicolon() {
- return JavaCore.INSERT.equals(this.compilationUnit.getJavaProject().getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_SEMICOLON, true));
- }
-
- private void addPreferenceOrderHolders(PackageEntry[] preferenceOrder) {
- if (this.packageEntries.isEmpty()) {
- // all new: copy the elements
- for (int i= 0; i < preferenceOrder.length; i++) {
- this.packageEntries.add(preferenceOrder[i]);
- }
- } else {
- // match the preference order entries to existing imports
- // entries not found are appended after the last successfully matched entry
-
- PackageEntry[] lastAssigned= new PackageEntry[preferenceOrder.length];
-
- // find an existing package entry that matches most
- for (int k= 0; k < this.packageEntries.size(); k++) {
- PackageEntry entry= (PackageEntry) this.packageEntries.get(k);
- if (!entry.isComment()) {
- String currName= entry.getName();
- int currNameLen= currName.length();
- int bestGroupIndex= -1;
- int bestGroupLen= -1;
- for (int i= 0; i < preferenceOrder.length; i++) {
- boolean currPrevStatic= preferenceOrder[i].isStatic();
- if (currPrevStatic == entry.isStatic()) {
- String currPrefEntry= preferenceOrder[i].getName();
- int currPrefLen= currPrefEntry.length();
- if (currName.startsWith(currPrefEntry) && currPrefLen >= bestGroupLen) {
- if (currPrefLen == currNameLen || currName.charAt(currPrefLen) == '.') {
- if (bestGroupIndex == -1 || currPrefLen > bestGroupLen) {
- bestGroupLen= currPrefLen;
- bestGroupIndex= i;
- }
- }
- }
- }
- }
- if (bestGroupIndex != -1) {
- entry.setGroupID(preferenceOrder[bestGroupIndex].getName());
- lastAssigned[bestGroupIndex]= entry; // remember last entry
- }
- }
- }
- // fill in not-assigned categories, keep partial order
- int currAppendIndex= 0;
- for (int i= 0; i < lastAssigned.length; i++) {
- PackageEntry entry= lastAssigned[i];
- if (entry == null) {
- PackageEntry newEntry= preferenceOrder[i];
- if (currAppendIndex == 0 && !newEntry.isStatic()) {
- currAppendIndex= getIndexAfterStatics();
- }
- this.packageEntries.add(currAppendIndex, newEntry);
- currAppendIndex++;
- } else {
- currAppendIndex= this.packageEntries.indexOf(entry) + 1;
- }
- }
- }
- }
-
- private String getQualifier(ImportDeclaration decl) {
- String name = decl.getName().getFullyQualifiedName();
- /*
- * If it's on demand import, return the fully qualified name. (e.g. pack1.Foo.* => pack.Foo, pack1.* => pack1)
- * This is because we need to have pack1.Foo.* and pack1.Bar under different qualifier groups.
- */
- if (decl.isOnDemand()) {
- return name;
- }
- return getQualifier(name, decl.isStatic());
- }
-
- private String getQualifier(String name, boolean isStatic) {
- // For static imports, return the Type name as well as part of the qualifier
- if (isStatic || !this.useContextToFilterImplicitImports) {
- return Signature.getQualifier(name);
- }
-
- char[] searchedName = name.toCharArray();
- int index = name.length();
- /* Stop at the last fragment */
- JavaProject project = (JavaProject) this.compilationUnit.getJavaProject();
- do {
- String testedName = new String(searchedName, 0, index);
- IJavaElement fragment = null;
- try {
- fragment = project.findPackageFragment(testedName);
- } catch (JavaModelException e) {
- return name;
- }
- if (fragment != null) {
- return testedName;
- }
- try {
- fragment = project.findType(testedName);
- } catch (JavaModelException e) {
- return name;
- }
- if (fragment != null) {
- index = CharOperation.lastIndexOf(Signature.C_DOT, searchedName, 0, index - 1);
- } else {
- // we use the heuristic that a name starting with a lowercase is a package name
- index = CharOperation.lastIndexOf(Signature.C_DOT, searchedName, 0, index - 1);
- if (Character.isLowerCase(searchedName[index + 1])) {
- return testedName;
- }
- }
- } while (index >= 0);
- return name;
- }
-
- private static String getFullName(ImportDeclaration decl) {
- String name= decl.getName().getFullyQualifiedName();
- return decl.isOnDemand() ? name + ".*": name; //$NON-NLS-1$
- }
-
- private void addExistingImports(CompilationUnit root) {
- List/*ImportDeclaration*/ decls= root.imports();
- if (decls.isEmpty()) {
- return;
- }
- PackageEntry currPackage= null;
-
- ImportDeclaration curr= (ImportDeclaration) decls.get(0);
- int currOffset= curr.getStartPosition();
- int currLength= curr.getLength();
- int currEndLine= root.getLineNumber(currOffset + currLength);
-
- for (int i= 1; i < decls.size(); i++) {
- boolean isStatic= curr.isStatic();
- String name= getFullName(curr);
- String packName= getQualifier(curr);
- if (currPackage == null || currPackage.compareTo(packName, isStatic) != 0) {
- currPackage= new PackageEntry(packName, null, isStatic);
- this.packageEntries.add(currPackage);
- }
-
- ImportDeclaration next= (ImportDeclaration) decls.get(i);
- int nextOffset= next.getStartPosition();
- int nextLength= next.getLength();
- int nextOffsetLine= root.getLineNumber(nextOffset);
-
- int extendedStart = root.getExtendedStartPosition(curr);
- int extendedLength = root.getExtendedLength(curr);
- if (extendedStart < this.replaceRange.getOffset()) {
- // don't touch the first comments before the start of import declarations
- extendedLength -= (currOffset - extendedStart);
- extendedStart = currOffset;
- }
-
- // if next import is on a different line, modify the end position to the next line begin offset
- int nextLineOffset = nextOffset; // offset at the start of next line. Next import may not start here
- if (currEndLine < nextOffsetLine) {
- currEndLine++;
- nextLineOffset = root.getPosition(currEndLine, 0);
- // There may be some leading comments (or line delimiters) before the next import. The start of those comments
- // is not the real start offset of the next import. So don't change nextOffset
- }
- // retrieve preceding and trailing comments if any
- IRegion rangeBefore = null;
- IRegion rangeAfter = null;
-
- if (currOffset > extendedStart) {
- rangeBefore = new Region(extendedStart, currOffset - extendedStart);
- }
- int currLen = curr.getLength();
- if (currLen < extendedLength - (currOffset - extendedStart)) {
- int currEndOffset = currOffset + currLen;
- int rangeBeforeLen = rangeBefore != null? rangeBefore.getLength() : 0;
- rangeAfter = new Region(currEndOffset, extendedLength - rangeBeforeLen - currLen);
- }
- currPackage.add(
- new ImportDeclEntry(
- packName.length(),
- name,
- isStatic,
- new Region(currOffset, nextLineOffset - currOffset), // should not include leading comments of next import, line delimiters, etc.
- rangeBefore,
- rangeAfter));
- currOffset= nextOffset;
- curr= next;
-
- // add a comment entry for spacing between imports
- if (currEndLine < nextOffsetLine) {
- nextOffset= root.getPosition(nextOffsetLine, 0);
-
- int length = nextOffset - nextLineOffset;
- if (length > 2) { // valid comment has at least two chars
- currPackage.add(new ImportDeclEntry(packName.length(), null, false, new Region(nextLineOffset, length)));
- }
-
- currOffset= nextOffset;
- }
- currEndLine= root.getLineNumber(nextOffset + nextLength);
- }
-
- boolean isStatic= curr.isStatic();
- String name= getFullName(curr);
- String packName= getQualifier(curr);
- if (currPackage == null || currPackage.compareTo(packName, isStatic) != 0) {
- currPackage= new PackageEntry(packName, null, isStatic);
- this.packageEntries.add(currPackage);
- }
- int currStartOffset = curr.getStartPosition();
- int currLen = curr.getLength();
- int extendedStartOffset = root.getExtendedStartPosition(curr);
- IRegion leadingComments = null;
- IRegion allTrailingComments = null;
-
- if (currStartOffset > extendedStartOffset) {
- leadingComments = new Region(extendedStartOffset, currOffset - extendedStartOffset);
- }
- int length= this.replaceRange.getOffset() + this.replaceRange.getLength() - currStartOffset;
- int extendedLength = root.getExtendedLength(curr);
- if (currLen < extendedLength - (currOffset - extendedStartOffset)) {
- int currEndOffset = currOffset + currLen;
- int leadingCommentsLen = leadingComments != null? leadingComments.getLength() : 0;
- allTrailingComments = new Region(currEndOffset, extendedLength - leadingCommentsLen - currLen);
- }
- currPackage.add(new ImportDeclEntry(packName.length(), name, isStatic, new Region(curr.getStartPosition(), length), leadingComments, allTrailingComments));
- }
-
- private IRegion[] retrieveExistingCommentsInImports(CompilationUnit root) {
- List/*ImportDeclaration*/ decls= root.imports();
- if (decls.isEmpty()) {
- return null;
- }
-
- List commentList = root.getCommentList();
- int numberOfComments = commentList.size();
- List regions = null;
- int currentExtendedEnd = -1;
- int currEndLine= -1;
-
- /* for the first comment, we only take the trailing comment if any and the replace range doesn't
- * include the preceding comment
- */
- for (int i= 0; i < decls.size(); i++) {
- ImportDeclaration next= (ImportDeclaration) decls.get(i);
- int nextOffset= next.getStartPosition();
- int nextLength= next.getLength();
-
- int extendedStart = root.getExtendedStartPosition(next);
- int extendedLength = root.getExtendedLength(next);
- int nextOffsetLine= root.getLineNumber(nextOffset);
-
- if (nextOffset != extendedStart) {
- // preceding comment
- int lengthOfPrecedingComment = nextOffset - extendedStart;
- if (i != 0) {
- if (regions == null) {
- regions = new ArrayList();
- }
- regions.add(new Region(extendedStart, lengthOfPrecedingComment));
- }
-
- if (extendedLength != (nextLength + lengthOfPrecedingComment)) {
- // Preceding and trailing comments
- int regionLength = extendedLength - (nextLength + lengthOfPrecedingComment);
- if (regions == null) {
- regions = new ArrayList();
- }
- regions.add(new Region(nextOffset + nextLength, regionLength));
- }
- } else if (nextLength != extendedLength) {
- // no extended start - only trailing comment
- int regionLength = extendedLength - nextLength;
- if (regions == null) {
- regions = new ArrayList();
- }
- regions.add(new Region(nextOffset + nextLength, regionLength));
- }
- if (i > 0) {
- // record comments between the previous comment and the current one that are not part
- // of any comment extended range.
- if ((nextOffsetLine - currEndLine) > 1) {
- // check for comments between the two imports
- LineComment comment = root.getAST().newLineComment();
- comment.setSourceRange(currentExtendedEnd + 1, 0);
- int index = Collections.binarySearch(commentList, comment, new Comparator() {
- public int compare(Object o1, Object o2) {
- return ((Comment) o1).getStartPosition() - ((Comment) o2).getStartPosition();
- }
- });
- // index = -(insertion point) - 1.
- if (index < 0) {
- loop: for (int j = -(index + 1); j < numberOfComments; j++) {
- Comment currentComment = (Comment) commentList.get(j);
- int commentStartPosition = currentComment.getStartPosition();
- int commentLength = currentComment.getLength();
- if ((commentStartPosition > currentExtendedEnd)
- && ((commentStartPosition + commentLength - 1) < extendedStart)) {
- if (regions == null) {
- regions = new ArrayList();
- }
- regions.add(new Region(commentStartPosition, commentLength));
- } else {
- break loop;
- }
- }
- }
- }
- }
- currentExtendedEnd = extendedStart + extendedLength - 1;
- currEndLine = root.getLineNumber(currentExtendedEnd);
- }
- if (regions == null) {
- return null;
- }
- // sort regions according to their positions to restore comments in the same order
- IRegion[] result = (IRegion[]) regions.toArray(new IRegion[regions.size()]);
- Arrays.sort(result, new Comparator() {
- public int compare(Object o1, Object o2) {
- return ((IRegion) o1).getOffset() - ((IRegion) o2).getOffset();
- }
- });
- return result;
- }
- /**
- * Specifies that implicit imports (for types in <code>java.lang</code>, types in the same package as the rewrite
- * compilation unit and types in the compilation unit's main type) should not be created, except if necessary to
- * resolve an on-demand import conflict.
- * <p>
- * The filter is enabled by default.
- * </p>
- * <p>
- * Note: {@link #ImportRewriteAnalyzer(ICompilationUnit, CompilationUnit, String[], int, int, boolean, boolean)} with true as the last
- * parameter can be used to filter implicit imports when a context is used.
- * </p>
- *
- * @param filterImplicitImports
- * if <code>true</code>, implicit imports will be filtered
- *
- * @see #ImportRewriteAnalyzer(ICompilationUnit, CompilationUnit, String[], int, int, boolean, boolean)
- */
- public void setFilterImplicitImports(boolean filterImplicitImports) {
- this.filterImplicitImports= filterImplicitImports;
- }
- /**
- * When set searches for imports that can not be folded into on-demand
- * imports but must be specified explicitly
- * @param findAmbiguousImports The new value
- */
- public void setFindAmbiguousImports(boolean findAmbiguousImports) {
- this.findAmbiguousImports= findAmbiguousImports;
- }
-
- private static class PackageMatcher {
- private String newName;
- private String bestName;
- private int bestMatchLen;
-
- public PackageMatcher() {
- // initialization in 'initialize'
- }
-
- public void initialize(String newImportName, String bestImportName) {
- this.newName= newImportName;
- this.bestName= bestImportName;
- this.bestMatchLen= getCommonPrefixLength(bestImportName, newImportName);
- }
-
- public boolean isBetterMatch(String currName, boolean preferCurr) {
- boolean isBetter;
- int currMatchLen= getCommonPrefixLength(currName, this.newName);
- int matchDiff= currMatchLen - this.bestMatchLen;
- if (matchDiff == 0) {
- if (currMatchLen == this.newName.length() && currMatchLen == currName.length() && currMatchLen == this.bestName.length()) {
- // duplicate entry and complete match
- isBetter= preferCurr;
- } else {
- isBetter= sameMatchLenTest(currName);
- }
- } else {
- isBetter= (matchDiff > 0); // curr has longer match
- }
- if (isBetter) {
- this.bestName= currName;
- this.bestMatchLen= currMatchLen;
- }
- return isBetter;
- }
-
- private boolean sameMatchLenTest(String currName) {
- int matchLen= this.bestMatchLen;
- // known: bestName and currName differ from newName at position 'matchLen'
- // currName and bestName don't have to differ at position 'matchLen'
-
- // determine the order and return true if currName is closer to newName
- char newChar= getCharAt(this.newName, matchLen);
- char currChar= getCharAt(currName, matchLen);
- char bestChar= getCharAt(this.bestName, matchLen);
-
- if (newChar < currChar) {
- if (bestChar < newChar) { // b < n < c
- return (currChar - newChar) < (newChar - bestChar); // -> (c - n) < (n - b)
- } else { // n < b && n < c
- if (currChar == bestChar) { // longer match between curr and best
- return false; // keep curr and best together, new should be before both
- } else {
- return currChar < bestChar; // -> (c < b)
- }
- }
- } else {
- if (bestChar > newChar) { // c < n < b
- return (newChar - currChar) < (bestChar - newChar); // -> (n - c) < (b - n)
- } else { // n > b && n > c
- if (currChar == bestChar) { // longer match between curr and best
- return true; // keep curr and best together, new should be ahead of both
- } else {
- return currChar > bestChar; // -> (c > b)
- }
- }
- }
- }
- }
-
- /* package */ static int getCommonPrefixLength(String s, String t) {
- int len= Math.min(s.length(), t.length());
- for (int i= 0; i < len; i++) {
- if (s.charAt(i) != t.charAt(i)) {
- return i;
- }
- }
- return len;
- }
-
- /* package */ static char getCharAt(String str, int index) {
- if (str.length() > index) {
- return str.charAt(index);
- }
- return 0;
- }
-
- private PackageEntry findBestMatch(String newName, boolean isStatic) {
- if (this.packageEntries.isEmpty()) {
- return null;
- }
- String groupId= null;
- int longestPrefix= -1;
- PackageEntry matchingCommentEntry = null;
- // find the matching group
- for (int i= 0; i < this.packageEntries.size(); i++) {
- PackageEntry curr= (PackageEntry) this.packageEntries.get(i);
- if (isStatic == curr.isStatic()) {
- String currGroup= curr.getGroupID();
- if (currGroup != null && newName.startsWith(currGroup)) {
- int prefixLen= currGroup.length();
- if (prefixLen == newName.length() && !curr.isComment()) {
- return curr; // perfect fit, use entry
- } else if (curr.isComment()) {
- matchingCommentEntry = curr; // may be the only fit if no actual import of this group is already present
- continue;
- }
- if ((newName.charAt(prefixLen) == '.' || prefixLen == 0) && prefixLen > longestPrefix) {
- longestPrefix= prefixLen;
- groupId= currGroup;
- }
- }
- }
- }
- if (matchingCommentEntry != null) {
- return matchingCommentEntry;
- }
- PackageEntry bestMatch= null;
- PackageMatcher matcher= new PackageMatcher();
- matcher.initialize(newName, ""); //$NON-NLS-1$
- for (int i= 0; i < this.packageEntries.size(); i++) { // find the best match with the same group
- PackageEntry curr= (PackageEntry) this.packageEntries.get(i);
- if (!curr.isComment() && curr.isStatic() == isStatic) {
- if (groupId == null || groupId.equals(curr.getGroupID())) {
- boolean preferrCurr= (bestMatch == null) || (curr.getNumberOfImports() > bestMatch.getNumberOfImports());
- if (matcher.isBetterMatch(curr.getName(), preferrCurr)) {
- bestMatch= curr;
- }
- }
- }
- }
- return bestMatch;
- }
-
- private boolean isImplicitImport(String qualifier) {
- if (JAVA_LANG.equals(qualifier)) {
- return true;
- }
- ICompilationUnit cu= this.compilationUnit;
- String packageName= cu.getParent().getElementName();
- if (qualifier.equals(packageName)) {
- return true;
- }
- String mainTypeName= JavaCore.removeJavaLikeExtension(cu.getElementName());
- if (packageName.length() == 0) {
- return qualifier.equals(mainTypeName);
- }
- return qualifier.equals(packageName +'.' + mainTypeName);
- }
-
- public void addImport(String fullTypeName, boolean isStatic, CompilationUnit root, boolean restoreExistingImports) {
- String typeContainerName= getQualifier(fullTypeName, isStatic);
- ImportDeclEntry decl;
- if (restoreExistingImports) {
- decl = new ImportDeclEntry(typeContainerName.length(), fullTypeName, isStatic, null);
- } else {
- decl = addImportDeclEntry(typeContainerName, fullTypeName, isStatic, root);
- }
- sortIn(typeContainerName, decl, isStatic);
- }
-
- /**
- * adds the import entry, but if its an existing import entry then preserves the comments surrounding the import
- */
- private ImportDeclEntry addImportDeclEntry(String containerName, String fullTypeName, boolean isStatic, CompilationUnit root) {
- List/*ImportDeclaration*/ decls= root.imports();
- if (decls.isEmpty() || this.preserveExistingCommentsRanges == null || this.preserveExistingCommentsRanges.length == 0) {
- return new ImportDeclEntry(containerName.length(), fullTypeName, isStatic, null);
- }
- IRegion precedingCommentRange = null;
- IRegion trailingCommentRange = null;
- int prevOffset = this.replaceRange.getOffset(); // will store offset of the previous import's extended end
- int numOfImports = decls.size();
- for (int i= 0; i < numOfImports; i++) {
- ImportDeclaration curr= (ImportDeclaration) decls.get(i);
- int currOffset= curr.getStartPosition();
- int currLength= curr.getLength();
- int currExtendedStart = root.getExtendedStartPosition(curr);
- int currExtendedLen = root.getExtendedLength(curr);
- String name= getFullName(curr);
- String packName= getQualifier(curr);
- if (packName.equals(containerName) && (name.equals(fullTypeName) || name.endsWith("*"))) {//$NON-NLS-1$
- int preserveCommentsLen = this.preserveExistingCommentsRanges.length;
- for (int j = 0; j < preserveCommentsLen; j++) {
- int offset = this.preserveExistingCommentsRanges[j].getOffset();
- boolean wasRangeUsed = false;
- int existingCommentLength = this.preserveExistingCommentsRanges[j].getLength();
- if (offset == currExtendedStart) {
- // comments belonging to this import's extended start
- precedingCommentRange = new Region(offset, existingCommentLength);
- wasRangeUsed = true;
- } else if (offset < currExtendedStart && offset > prevOffset) {
- // comment between two imports but not inside either's extended ranges
- // to preserve the position of these comments add a dummy comment entry
- PackageEntry commentEntry = new PackageEntry(); // create a comment package entry for this
- commentEntry.setGroupID(packName); // the comment should belong to the current group
- this.packageEntries.add(commentEntry);
- commentEntry.add(new ImportDeclEntry(packName.length(), null, false, new Region(offset, existingCommentLength)));
- wasRangeUsed = true;
- } else if ((currExtendedStart + currExtendedLen) != (currOffset + currLength)){
- if (offset == currOffset + currLength) {
- // comment is in the extended end of the import
- trailingCommentRange = new Region(offset, existingCommentLength);
- wasRangeUsed = true;
- } else if (offset > (currOffset + currLength)) {
- break;
- }
- }
- if (wasRangeUsed) {
- // remove this comment from preserveExistingCommentsRanges array
- IRegion[] tempRegions = new IRegion[--preserveCommentsLen];
- System.arraycopy(this.preserveExistingCommentsRanges, 0, tempRegions, 0, j);
- System.arraycopy(this.preserveExistingCommentsRanges, j+1, tempRegions, j, tempRegions.length - j);
- this.preserveExistingCommentsRanges = tempRegions;
- j--;
- }
- }
- return new ImportDeclEntry(containerName.length(), fullTypeName, isStatic, null, precedingCommentRange, trailingCommentRange);
- }
- prevOffset = currExtendedStart + currExtendedLen - 1;
- }
- return new ImportDeclEntry(containerName.length(), fullTypeName, isStatic, null);
- }
-
- public boolean removeImport(String qualifiedName, boolean isStatic) {
- String containerName= getQualifier(qualifiedName, isStatic);
-
- int nPackages= this.packageEntries.size();
- for (int i= 0; i < nPackages; i++) {
- PackageEntry entry= (PackageEntry) this.packageEntries.get(i);
- if (entry.compareTo(containerName, isStatic) == 0) {
- if (entry.remove(qualifiedName, isStatic)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private int getIndexAfterStatics() {
- for (int i= 0; i < this.packageEntries.size(); i++) {
- if (!((PackageEntry) this.packageEntries.get(i)).isStatic()) {
- return i;
- }
- }
- return this.packageEntries.size();
- }
-
-
- private void sortIn(String typeContainerName, ImportDeclEntry decl, boolean isStatic) {
- PackageEntry bestMatch= findBestMatch(typeContainerName, isStatic);
- if (bestMatch == null) {
- PackageEntry packEntry= new PackageEntry(typeContainerName, null, isStatic);
- packEntry.add(decl);
- int insertPos= packEntry.isStatic() ? 0 : getIndexAfterStatics();
- this.packageEntries.add(insertPos, packEntry);
- } else {
- int cmp= typeContainerName.compareTo(bestMatch.getName());
- if (cmp == 0) {
- bestMatch.sortIn(decl);
- } else {
- // create a new package entry
- String group= bestMatch.getGroupID();
- if (group != null) {
- if (!typeContainerName.startsWith(group)) {
- group= null;
- }
- }
- PackageEntry packEntry= new PackageEntry(typeContainerName, group, isStatic);
- packEntry.add(decl);
- int index= this.packageEntries.indexOf(bestMatch);
- if (cmp < 0) {
- // insert ahead of best match
- this.packageEntries.add(index, packEntry);
- } else {
- // insert after best match
- this.packageEntries.add(index + 1, packEntry);
- }
- }
- }
- }
-
- private IRegion evaluateReplaceRange(CompilationUnit root) {
- List imports= root.imports();
- if (!imports.isEmpty()) {
- ImportDeclaration first= (ImportDeclaration) imports.get(0);
- ImportDeclaration last= (ImportDeclaration) imports.get(imports.size() - 1);
-
- int startPos= first.getStartPosition(); // no extended range for first: bug 121428
- int endPos= root.getExtendedStartPosition(last) + root.getExtendedLength(last);
- int endLine= root.getLineNumber(endPos);
- if (endLine > 0) {
- int nextLinePos= root.getPosition(endLine + 1, 0);
- if (nextLinePos >= 0) {
- int firstTypePos= getFirstTypeBeginPos(root);
- if (firstTypePos != -1 && firstTypePos < nextLinePos) {
- endPos= firstTypePos;
- } else {
- endPos= nextLinePos;
- }
- }
- }
- return new Region(startPos, endPos - startPos);
- } else {
- int start= getPackageStatementEndPos(root);
- return new Region(start, 0);
- }
- }
-
- public MultiTextEdit getResultingEdits(IProgressMonitor monitor) throws JavaModelException {
- if (monitor == null) {
- monitor= new NullProgressMonitor();
- }
- try {
- int importsStart= this.replaceRange.getOffset();
- int importsLen= this.replaceRange.getLength();
-
- String lineDelim= this.compilationUnit.findRecommendedLineSeparator();
- IBuffer buffer= this.compilationUnit.getBuffer();
-
- int currPos= importsStart;
- MultiTextEdit resEdit= new MultiTextEdit();
-
- if ((this.flags & F_NEEDS_LEADING_DELIM) != 0) {
- // new import container
- resEdit.addChild(new InsertEdit(currPos, lineDelim));
- }
-
- PackageEntry lastPackage= null;
-
- Set onDemandConflicts= null;
- if (this.findAmbiguousImports) {
- onDemandConflicts= evaluateStarImportConflicts(monitor);
- }
-
- int spacesBetweenGroups= getSpacesBetweenImportGroups();
-
- ArrayList stringsToInsert= new ArrayList();
-
- int nPackageEntries= this.packageEntries.size();
- for (int i= 0; i < nPackageEntries; i++) {
- PackageEntry pack= (PackageEntry) this.packageEntries.get(i);
- if (this.filterImplicitImports && !pack.isStatic() && isImplicitImport(pack.getName())) {
- pack.filterImplicitImports(this.useContextToFilterImplicitImports);
- }
- int nImports= pack.getNumberOfImports();
- if (nImports == 0) {
- continue;
- }
-
- if (spacesBetweenGroups > 0 && lastPackage != null) {
- // add a space between two different groups by looking at the two adjacent imports
- if (!lastPackage.isComment() && !pack.isComment() && !pack.isSameGroup(lastPackage)) {
- for (int k= spacesBetweenGroups; k > 0; k--) {
- stringsToInsert.add(lineDelim);
- }
- } else if (lastPackage.isComment() && pack.isSameGroup(lastPackage)) {
- // the last pack may be a dummy for a comment which doesn't belong to any extended range
- stringsToInsert.add(lineDelim);
- }
- }
- lastPackage= pack;
-
- boolean isStatic= pack.isStatic();
- int threshold= isStatic ? this.staticImportOnDemandThreshold : this.importOnDemandThreshold;
-
- boolean doStarImport= pack.hasStarImport(threshold, onDemandConflicts);
- boolean allImportsAddedToStar = false;
- if (doStarImport && (pack.find("*") == null)) { //$NON-NLS-1$
- String[] imports = getNewImportStrings(buffer, pack, isStatic, lineDelim);
- for (int j = 0, max = imports.length; j < max; j++) {
- stringsToInsert.add(imports[j]);
- }
- allImportsAddedToStar = true; // may still need to handle onDemandConflicts below
- }
-
- for (int k= 0; k < nImports; k++) {
- ImportDeclEntry currDecl= pack.getImportAt(k);
- IRegion region= currDecl.getSourceRange();
- boolean isConflict = !currDecl.isComment() && onDemandConflicts != null && onDemandConflicts.contains(currDecl.getSimpleName());
- boolean addRegularToStar = doStarImport && !currDecl.isOnDemand();
-
- if (region == null) { // new entry
- if (!addRegularToStar || isConflict) {
- IRegion rangeBefore = currDecl.getPrecedingCommentRange();
- IRegion rangeAfter = currDecl.getTrailingCommentRange();
- if (rangeBefore != null) {
- stringsToInsert.add(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
-
- String trailingComment = null;
- if (rangeAfter != null) {
- trailingComment = buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength());
- }
- String str= getNewImportString(currDecl.getElementName(), isStatic, trailingComment, lineDelim);
- stringsToInsert.add(str);
- } else if (addRegularToStar && !allImportsAddedToStar) {
- String simpleName = currDecl.getTypeQualifiedName();
- if (simpleName.indexOf('.') != -1) {
- String str= getNewImportString(currDecl.getElementName(), isStatic, lineDelim);
- if (stringsToInsert.indexOf(str) == -1) {
- stringsToInsert.add(str);
- }
- }
- }
- } else if (!addRegularToStar || isConflict) {
- int offset= region.getOffset();
- IRegion rangeBefore = currDecl.getPrecedingCommentRange();
- if (rangeBefore != null && currPos > rangeBefore.getOffset()) {
- // moved ahead of the leading comments, bring the currPos back
- currPos = rangeBefore.getOffset();
- }
- if (rangeBefore != null) {
- stringsToInsert.add(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
- removeAndInsertNew(buffer, currPos, offset, stringsToInsert, resEdit);
- stringsToInsert.clear();
- currPos= offset + region.getLength();
- } else if (addRegularToStar && !allImportsAddedToStar && !currDecl.isComment()) {
- String simpleName = currDecl.getTypeQualifiedName();
- if (simpleName.indexOf('.') != -1) {
- IRegion rangeBefore = currDecl.getPrecedingCommentRange();
- if (rangeBefore != null && currPos > rangeBefore.getOffset()) {
- // moved ahead of the leading comments, bring the currPos back
- currPos = rangeBefore.getOffset();
- }
- if (rangeBefore != null) {
- stringsToInsert.add(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
- IRegion rangeAfter = currDecl.getTrailingCommentRange();
- String trailingComment = null;
- if (rangeAfter != null) {
- trailingComment = buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength());
- }
- String str= getNewImportString(currDecl.getElementName(), isStatic, trailingComment, lineDelim);
- if (stringsToInsert.indexOf(str) == -1) {
- stringsToInsert.add(str);
- }
- }
- }
- }
- }
-
- // insert back all existing imports comments since existing imports were not preserved
- if (this.preserveExistingCommentsRanges != null) {
- for (int i = 0, max = this.preserveExistingCommentsRanges.length; (i < max && this.preserveExistingCommentsRanges[i] != null); i++) {
- IRegion region = this.preserveExistingCommentsRanges[i];
- String text = buffer.getText(region.getOffset(), region.getLength());
- // remove preceding whitespaces
- int index = 0;
- int length = text.length();
- loop: while (index < length) {
- if (Character.isWhitespace(text.charAt(index))) {
- index++;
- } else {
- break loop;
- }
- }
- if (index != 0) {
- text = text.substring(index);
- }
- if (!text.endsWith(lineDelim)) {
- text += lineDelim;
- }
- stringsToInsert.add(text);
- }
- }
- int end= importsStart + importsLen;
- removeAndInsertNew(buffer, currPos, end, stringsToInsert, resEdit);
-
- if (importsLen == 0) {
- if (!this.importsCreated.isEmpty() || !this.staticImportsCreated.isEmpty()) { // new import container
- if ((this.flags & F_NEEDS_TRAILING_DELIM) != 0) {
- resEdit.addChild(new InsertEdit(currPos, lineDelim));
- }
- } else {
- return new MultiTextEdit(); // no changes
- }
- }
- return resEdit;
- } finally {
- monitor.done();
- }
- }
-
- private void removeAndInsertNew(IBuffer buffer, int contentOffset, int contentEnd, ArrayList stringsToInsert, MultiTextEdit resEdit) {
- int pos= contentOffset;
- for (int i= 0; i < stringsToInsert.size(); i++) {
- String curr= (String) stringsToInsert.get(i);
- int idx= findInBuffer(buffer, curr, pos, contentEnd);
- if (idx != -1) {
- if (idx != pos) {
- resEdit.addChild(new DeleteEdit(pos, idx - pos));
- }
- pos= idx + curr.length();
- } else {
- resEdit.addChild(new InsertEdit(pos, curr));
- }
- }
- if (pos < contentEnd) {
- resEdit.addChild(new DeleteEdit(pos, contentEnd - pos));
- }
- }
-
- private int findInBuffer(IBuffer buffer, String str, int start, int end) {
- int pos= start;
- int len= str.length();
- if (pos + len > end || str.length() == 0) {
- return -1;
- }
- char first= str.charAt(0);
- int step= str.indexOf(first, 1);
- if (step == -1) {
- step= len;
- }
- while (pos + len <= end) {
- if (buffer.getChar(pos) == first) {
- int k= 1;
- while (k < len && buffer.getChar(pos + k) == str.charAt(k)) {
- k++;
- }
- if (k == len) {
- return pos; // found
- }
- if (k < step) {
- pos+= k;
- } else {
- pos+= step;
- }
- } else {
- pos++;
- }
- }
- return -1;
- }
-
- private Set evaluateStarImportConflicts(IProgressMonitor monitor) throws JavaModelException {
- //long start= System.currentTimeMillis();
-
- final HashSet/*String*/ onDemandConflicts= new HashSet();
-
- IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaElement[] { this.compilationUnit.getJavaProject() });
-
- ArrayList/*<char[][]>*/ starImportPackages= new ArrayList();
- ArrayList/*<char[][]>*/ simpleTypeNames= new ArrayList();
- int nPackageEntries= this.packageEntries.size();
- for (int i= 0; i < nPackageEntries; i++) {
- PackageEntry pack= (PackageEntry) this.packageEntries.get(i);
- if (!pack.isStatic() && pack.hasStarImport(this.importOnDemandThreshold, null)) {
- starImportPackages.add(pack.getName().toCharArray());
- for (int k= 0; k < pack.getNumberOfImports(); k++) {
- ImportDeclEntry curr= pack.getImportAt(k);
- if (!curr.isOnDemand() && !curr.isComment()) {
- simpleTypeNames.add(curr.getSimpleName().toCharArray());
- }
- }
- }
- }
- if (starImportPackages.isEmpty()) {
- return null;
- }
-
- starImportPackages.add(this.compilationUnit.getParent().getElementName().toCharArray());
- starImportPackages.add(JAVA_LANG.toCharArray());
-
- char[][] allPackages= (char[][]) starImportPackages.toArray(new char[starImportPackages.size()][]);
- char[][] allTypes= (char[][]) simpleTypeNames.toArray(new char[simpleTypeNames.size()][]);
-
- TypeNameRequestor requestor= new TypeNameRequestor() {
- HashMap foundTypes= new HashMap();
-
- private String getTypeContainerName(char[] packageName, char[][] enclosingTypeNames) {
- StringBuffer buf= new StringBuffer();
- buf.append(packageName);
- for (int i= 0; i < enclosingTypeNames.length; i++) {
- if (buf.length() > 0)
- buf.append('.');
- buf.append(enclosingTypeNames[i]);
- }
- return buf.toString();
- }
-
- public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path) {
- String name= new String(simpleTypeName);
- String containerName= getTypeContainerName(packageName, enclosingTypeNames);
-
- String oldContainer= (String) this.foundTypes.put(name, containerName);
- if (oldContainer != null && !oldContainer.equals(containerName)) {
- onDemandConflicts.add(name);
- }
- }
- };
- new SearchEngine().searchAllTypeNames(allPackages, allTypes, scope, requestor, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor);
- return onDemandConflicts;
- }
-
- private String getNewImportString(String importName, boolean isStatic, String lineDelim) {
- return getNewImportString(importName, isStatic, null, lineDelim);
- }
-
- private String getNewImportString(String importName, boolean isStatic, String trailingComment, String lineDelim) {
- StringBuffer buf= new StringBuffer();
- buf.append("import "); //$NON-NLS-1$
- if (isStatic) {
- buf.append("static "); //$NON-NLS-1$
- }
- buf.append(importName);
- if (insertSpaceBeforeSemicolon()) buf.append(' ');
- buf.append(';');
- if (trailingComment != null) {
- buf.append(trailingComment);
- }
- buf.append(lineDelim);
-
- if (isStatic) {
- this.staticImportsCreated.add(importName);
- } else {
- this.importsCreated.add(importName);
- }
- return buf.toString();
- }
-
- private String[] getNewImportStrings(IBuffer buffer, PackageEntry packageEntry, boolean isStatic, String lineDelim) {
- boolean isStarImportAdded = false;
- List allImports = new ArrayList();
- int nImports = packageEntry.getNumberOfImports();
- StringBuffer allComments = null;
- StringBuffer allCommentsLead = null;
- for (int i= 0; i < nImports; i++) {
- ImportDeclEntry curr= packageEntry.getImportAt(i);
- if (curr.isComment()) {
- IRegion rangeBefore = curr.getPrecedingCommentRange();
- if (rangeBefore != null) {
- allImports.add(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
- IRegion rangeAfter = curr.getTrailingCommentRange();
- String trailingComment = null;
- if (rangeAfter != null) {
- trailingComment = buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength());
- }
- if (trailingComment != null) {
- allImports.add(buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength()));
- }
- } else {
- String simpleName = curr.getTypeQualifiedName();
- if (simpleName.indexOf('.') != -1) {
- // member type imports - we preserve it
- IRegion rangeBefore = curr.getPrecedingCommentRange();
- if (rangeBefore != null) {
- allImports.add(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
- IRegion rangeAfter = curr.getTrailingCommentRange();
- String trailingComment = null;
- if (rangeAfter != null) {
- trailingComment = buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength());
- }
- allImports.add(getNewImportString(curr.getElementName(), isStatic, trailingComment, lineDelim));
- } else if (!isStarImportAdded) {
- String starImportString= packageEntry.getName() + ".*"; //$NON-NLS-1$
- // collect all comments
- IRegion rangeBefore = curr.getPrecedingCommentRange();
- if (rangeBefore != null) {
- allImports.add(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
- IRegion rangeAfter = curr.getTrailingCommentRange();
- String trailComments = null;
- if (rangeAfter != null) {
- trailComments = buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength());
- }
- allImports.add(getNewImportString(starImportString, isStatic, trailComments, lineDelim));
- isStarImportAdded = true;
- } else {
- // collect all comments
- IRegion rangeBefore = curr.getPrecedingCommentRange();
- if (rangeBefore != null) {
- if (allCommentsLead == null) {
- allCommentsLead = new StringBuffer();
- }
- allCommentsLead.append(buffer.getText(rangeBefore.getOffset(), rangeBefore.getLength()));
- }
- IRegion rangeAfter = curr.getTrailingCommentRange();
- if (rangeAfter != null) {
- if (allComments == null) {
- allComments = new StringBuffer();
- }
- allComments.append(buffer.getText(rangeAfter.getOffset(), rangeAfter.getLength()));
- }
- }
- }
- }
- if (allCommentsLead != null) {
- allImports.add(0, String.valueOf(allCommentsLead));
- }
- if (allComments != null) {
- allImports.add(String.valueOf(allComments.append(lineDelim)));
- }
- return (String[]) allImports.toArray(new String[allImports.size()]);
- }
-
- private static int getFirstTypeBeginPos(CompilationUnit root) {
- List types= root.types();
- if (!types.isEmpty()) {
- return root.getExtendedStartPosition(((ASTNode) types.get(0)));
- }
- return -1;
- }
-
- private int getPackageStatementEndPos(CompilationUnit root) {
- PackageDeclaration packDecl= root.getPackage();
- if (packDecl != null) {
- int afterPackageStatementPos= -1;
- int lineNumber= root.getLineNumber(packDecl.getStartPosition() + packDecl.getLength());
- if (lineNumber >= 0) {
- int lineAfterPackage= lineNumber + 1;
- afterPackageStatementPos= root.getPosition(lineAfterPackage, 0);
- }
- if (afterPackageStatementPos < 0) {
- this.flags|= F_NEEDS_LEADING_DELIM;
- return packDecl.getStartPosition() + packDecl.getLength();
- }
- int firstTypePos= getFirstTypeBeginPos(root);
- if (firstTypePos != -1 && firstTypePos <= afterPackageStatementPos) {
- this.flags|= F_NEEDS_TRAILING_DELIM;
- if (firstTypePos == afterPackageStatementPos) {
- this.flags|= F_NEEDS_LEADING_DELIM;
- }
- return firstTypePos;
- }
- this.flags|= F_NEEDS_LEADING_DELIM;
- return afterPackageStatementPos; // insert a line after after package statement
- }
- this.flags |= F_NEEDS_TRAILING_DELIM;
- return 0;
- }
-
- public String toString() {
- int nPackages= this.packageEntries.size();
- StringBuffer buf= new StringBuffer("\n-----------------------\n"); //$NON-NLS-1$
- for (int i= 0; i < nPackages; i++) {
- PackageEntry entry= (PackageEntry) this.packageEntries.get(i);
- if (entry.isStatic()) {
- buf.append("static "); //$NON-NLS-1$
- }
- buf.append(entry.toString());
- }
- return buf.toString();
- }
-
- private static final class ImportDeclEntry {
-
- private String elementName;
- private IRegion sourceRange;
- private final boolean isStatic;
- private int containerNameLength;
- IRegion precedingCommentRange;
- IRegion trailingCommentRange;
-
- public ImportDeclEntry(
- int containerNameLength,
- String elementName,
- boolean isStatic,
- IRegion sourceRange,
- IRegion precedingCommentRange,
- IRegion trailingCommentRange) {
- this(containerNameLength, elementName, isStatic, sourceRange);
- this.precedingCommentRange = precedingCommentRange;
- this.trailingCommentRange = trailingCommentRange;
- }
-
- public ImportDeclEntry(int containerNameLength, String elementName, boolean isStatic, IRegion sourceRange) {
- this.elementName= elementName;
- this.sourceRange= sourceRange;
- this.isStatic= isStatic;
- this.containerNameLength = containerNameLength;
- }
-
- public String getElementName() {
- return this.elementName;
- }
-
- public int compareTo(String fullName, boolean isStaticImport) {
- int cmp= this.elementName.compareTo(fullName);
- if (cmp == 0) {
- if (this.isStatic == isStaticImport) {
- return 0;
- }
- return this.isStatic ? -1 : 1;
- }
- return cmp;
- }
-
- public String getSimpleName() {
- return Signature.getSimpleName(this.elementName);
- }
-
- public String getTypeQualifiedName() {
- return this.elementName.substring(this.containerNameLength + 1);
- }
-
- public boolean isOnDemand() {
- return this.elementName != null && this.elementName.endsWith(".*"); //$NON-NLS-1$
- }
-
- public boolean isStatic() {
- return this.isStatic;
- }
-
- public boolean isNew() {
- return this.sourceRange == null;
- }
-
- public boolean isComment() {
- return this.elementName == null;
- }
-
- public IRegion getSourceRange() {
- return this.sourceRange;
- }
-
- public IRegion getPrecedingCommentRange() {
- return this.precedingCommentRange;
- }
-
- public IRegion getTrailingCommentRange() {
- return this.trailingCommentRange;
- }
- }
-
- /*
- * Internal element for the import structure: A container for imports
- * of all types from the same package
- */
- private final static class PackageEntry {
- private String name;
- private ArrayList importEntries;
- private String group;
- private boolean isStatic;
-
- /**
- * Comment package entry
- */
- public PackageEntry() {
- this("!", null, false); //$NON-NLS-1$
- }
-
- /**
- * @param name Name of the package entry. e.g. org.eclipse.jdt.ui, containing imports like
- * org.eclipse.jdt.ui.JavaUI.
- * @param group The index of the preference order entry assigned
- * different group id's will result in spacers between the entries
- */
- public PackageEntry(String name, String group, boolean isStatic) {
- this.name= name;
- this.importEntries= new ArrayList(5);
- this.group= group;
- this.isStatic= isStatic;
- }
-
- public boolean isStatic() {
- return this.isStatic;
- }
-
- public int compareTo(String otherName, boolean isOtherStatic) {
- int cmp= this.name.compareTo(otherName);
- if (cmp == 0) {
- if (this.isStatic == isOtherStatic) {
- return 0;
- }
- return this.isStatic ? -1 : 1;
- }
- return cmp;
- }
-
- public void sortIn(ImportDeclEntry imp) {
- String fullImportName= imp.getElementName();
- int insertPosition= -1;
- int nInports= this.importEntries.size();
- for (int i= 0; i < nInports; i++) {
- ImportDeclEntry curr= getImportAt(i);
- if (!curr.isComment()) {
- int cmp= curr.compareTo(fullImportName, imp.isStatic());
- if (cmp == 0) {
- return; // exists already
- } else if (cmp > 0 && insertPosition == -1) {
- insertPosition= i;
- }
- }
- }
- if (insertPosition == -1) {
- this.importEntries.add(imp);
- } else {
- this.importEntries.add(insertPosition, imp);
- }
- }
-
-
- public void add(ImportDeclEntry imp) {
- this.importEntries.add(imp);
- }
-
- public ImportDeclEntry find(String simpleName) {
- int nInports= this.importEntries.size();
- for (int i= 0; i < nInports; i++) {
- ImportDeclEntry curr= getImportAt(i);
- if (!curr.isComment()) {
- String currName= curr.getElementName();
- if (currName.endsWith(simpleName)) {
- int dotPos= currName.length() - simpleName.length() - 1;
- if ((dotPos == -1) || (dotPos > 0 && currName.charAt(dotPos) == '.')) {
- return curr;
- }
- }
- }
- }
- return null;
- }
-
- public boolean remove(String fullName, boolean isStaticImport) {
- int nInports= this.importEntries.size();
- for (int i= 0; i < nInports; i++) {
- ImportDeclEntry curr= getImportAt(i);
- if (!curr.isComment() && curr.compareTo(fullName, isStaticImport) == 0) {
- this.importEntries.remove(i);
- return true;
- }
- }
- return false;
- }
-
- public void filterImplicitImports(boolean useContextToFilterImplicitImports) {
- int nInports= this.importEntries.size();
- for (int i= nInports - 1; i >= 0; i--) {
- ImportDeclEntry curr= getImportAt(i);
- if (curr.isNew()) {
- if (!useContextToFilterImplicitImports) {
- this.importEntries.remove(i);
- } else {
- String elementName = curr.getElementName();
- int lastIndexOf = elementName.lastIndexOf('.');
- boolean internalClassImport = lastIndexOf > getName().length();
- if (!internalClassImport) {
- this.importEntries.remove(i);
- }
- }
- }
- }
- }
-
- public ImportDeclEntry getImportAt(int index) {
- return (ImportDeclEntry) this.importEntries.get(index);
- }
-
- public boolean hasStarImport(int threshold, Set explicitImports) {
- if (isComment() || isDefaultPackage()) { // can not star import default package
- return false;
- }
- int nImports= getNumberOfImports();
- int count= 0;
- boolean containsNew= false;
- for (int i= 0; i < nImports; i++) {
- ImportDeclEntry curr= getImportAt(i);
- if (curr.isOnDemand()) {
- return true;
- }
- if (!curr.isComment()) {
- count++;
- boolean isExplicit= !curr.isStatic() && (explicitImports != null) && explicitImports.contains(curr.getSimpleName());
- containsNew |= curr.isNew() && !isExplicit;
- }
- }
- return (count >= threshold) && containsNew;
- }
-
- public int getNumberOfImports() {
- return this.importEntries.size();
- }
-
- public String getName() {
- return this.name;
- }
-
- public String getGroupID() {
- return this.group;
- }
-
- public void setGroupID(String groupID) {
- this.group= groupID;
- }
-
- public boolean isSameGroup(PackageEntry other) {
- if (this.group == null) {
- return other.getGroupID() == null;
- } else {
- return this.group.equals(other.getGroupID()) && (this.isStatic == other.isStatic());
- }
- }
-
- public boolean isComment() {
- return "!".equals(this.name); //$NON-NLS-1$
- }
-
- public boolean isDefaultPackage() {
- return this.name.length() == 0;
- }
-
- public String toString() {
- StringBuffer buf= new StringBuffer();
- if (isComment()) {
- buf.append("comment\n"); //$NON-NLS-1$
- } else {
- buf.append(this.name); buf.append(", groupId: "); buf.append(this.group); buf.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
- int nImports= getNumberOfImports();
- for (int i= 0; i < nImports; i++) {
- ImportDeclEntry curr= getImportAt(i);
- buf.append(" "); //$NON-NLS-1$
- if (curr.isComment()) {
- buf.append("comment"); //$NON-NLS-1$
- } else {
- if (curr.isStatic()) {
- buf.append("static "); //$NON-NLS-1$
- }
- buf.append(curr.getTypeQualifiedName());
- }
- if (curr.isNew()) {
- buf.append(" (new)"); //$NON-NLS-1$
- }
- buf.append("\n"); //$NON-NLS-1$
- }
- }
- return buf.toString();
- }
- }
-
- public String[] getCreatedImports() {
- return (String[]) this.importsCreated.toArray(new String[this.importsCreated.size()]);
- }
-
- public String[] getCreatedStaticImports() {
- return (String[]) this.staticImportsCreated.toArray(new String[this.staticImportsCreated.size()]);
- }
-
-}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictIdentifier.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictIdentifier.java
new file mode 100644
index 0000000000..a5bcb0749b
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictIdentifier.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.JavaModelException;
+
+public class ConflictIdentifier {
+ /**
+ * Encapsulates those simple names (of type imports and of static imports) which would be
+ * imported from multiple on-demand or implicit import containers.
+ */
+ static final class Conflicts {
+ final Set<String> typeConflicts;
+ final Set<String> staticConflicts;
+
+ Conflicts(Set<String> typeConflicts, Set<String> staticConflicts) {
+ this.typeConflicts = Collections.unmodifiableSet(new HashSet<String>(typeConflicts));
+ this.staticConflicts = Collections.unmodifiableSet(new HashSet<String>(staticConflicts));
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Conflicts(type: %s; static: %s)", this.typeConflicts, this.staticConflicts); //$NON-NLS-1$
+ }
+ }
+
+ private final OnDemandComputer onDemandComputer;
+ private final TypeConflictingSimpleNameFinder typeConflictFinder;
+ private final StaticConflictingSimpleNameFinder staticConflictFinder;
+ private final Set<String> implicitImportContainers;
+
+ ConflictIdentifier(
+ OnDemandComputer onDemandComputer,
+ TypeConflictingSimpleNameFinder typeConflictFinder,
+ StaticConflictingSimpleNameFinder staticConflictFinder,
+ Set<String> implicitImportContainers) {
+ this.onDemandComputer = onDemandComputer;
+ this.typeConflictFinder = typeConflictFinder;
+ this.staticConflictFinder = staticConflictFinder;
+ this.implicitImportContainers = implicitImportContainers;
+ }
+
+ /**
+ * Identifies the simple names (of the elements of {@code imports}) which would be imported from
+ * multiple on-demand or implicit import containers.
+ *
+ * @param imports
+ * imports whose simple names are to be considered for conflicts
+ * @param addedImports
+ * imports which have been added as part of the rewrite (and could therefore trigger
+ * on-demand reductions; a subset of {@code imports}
+ * @param typeExplicitSimpleNames
+ * simple names of types which are already known to require explicit imports
+ * @param staticExplicitSimpleNames
+ * simple names of statics which are already known to require explicit imports
+ * @param progressMonitor
+ * a progress monitor used to track time spent searching for conflicts
+ * @return a {@link Conflicts} object encapsulating the found conflicting type and static names
+ * @throws JavaModelException if an error occurs while searching for declarations
+ */
+ Conflicts identifyConflicts(
+ Set<ImportName> imports,
+ Set<ImportName> addedImports,
+ Set<String> typeExplicitSimpleNames,
+ Set<String> staticExplicitSimpleNames,
+ IProgressMonitor progressMonitor) throws JavaModelException {
+ Collection<OnDemandReduction> onDemandCandidates = this.onDemandComputer.identifyPossibleReductions(
+ imports, addedImports, typeExplicitSimpleNames, staticExplicitSimpleNames);
+
+ Set<String> typeOnDemandContainers = new HashSet<String>(extractContainerNames(onDemandCandidates, false));
+ Set<String> staticOnDemandContainers = new HashSet<String>(extractContainerNames(onDemandCandidates, true));
+
+ if (!typeOnDemandContainers.isEmpty()) {
+ // Existing on-demands might conflict with new or existing on-demands.
+ typeOnDemandContainers.addAll(extractOnDemandContainerNames(imports, false));
+
+ // Implicitly imported types might conflict with type on-demands.
+ typeOnDemandContainers.addAll(this.implicitImportContainers);
+
+ // Member types imported by static on-demands might conflict with type on-demands.
+ typeOnDemandContainers.addAll(staticOnDemandContainers);
+ }
+
+ if (!staticOnDemandContainers.isEmpty()) {
+ // Existing on-demands might conflict with new or existing on-demands.
+ staticOnDemandContainers.addAll(extractOnDemandContainerNames(imports, true));
+ }
+
+ Set<String> typeConflicts = findConflictingSimpleNames(
+ this.typeConflictFinder, imports, false, typeOnDemandContainers, progressMonitor);
+
+ Set<String> staticConflicts = findConflictingSimpleNames(
+ this.staticConflictFinder, imports, true, staticOnDemandContainers, progressMonitor);
+
+ return new Conflicts(typeConflicts, staticConflicts);
+ }
+
+ private Collection<String> extractContainerNames(
+ Collection<OnDemandReduction> onDemandCandidates, boolean isStatic) {
+ Collection<String> containerNames = new ArrayList<String>(onDemandCandidates.size());
+ for (OnDemandReduction onDemandCandidate : onDemandCandidates) {
+ ImportName containerOnDemand = onDemandCandidate.containerOnDemand;
+ if (containerOnDemand.isStatic == isStatic) {
+ containerNames.add(containerOnDemand.containerName);
+ }
+ }
+
+ return containerNames;
+ }
+
+ private Collection<String> extractOnDemandContainerNames(
+ Collection<ImportName> imports, boolean isStatic) {
+ Collection<String> onDemandContainerNames = new ArrayList<String>(imports.size());
+ for (ImportName importName : imports) {
+ if (importName.isOnDemand() && importName.isStatic == isStatic) {
+ onDemandContainerNames.add(importName.containerName);
+ }
+ }
+
+ return onDemandContainerNames;
+ }
+
+ private Set<String> findConflictingSimpleNames(
+ ConflictingSimpleNameFinder conflictFinder,
+ Set<ImportName> imports,
+ boolean isStatic,
+ Set<String> onDemandImportedContainers,
+ IProgressMonitor monitor) throws JavaModelException {
+ if (onDemandImportedContainers.isEmpty() || imports.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set<String> simpleNames = new HashSet<String>();
+ for (ImportName currentImport : imports) {
+ if (currentImport.isStatic == isStatic) {
+ simpleNames.add(currentImport.simpleName);
+ }
+ }
+
+ return conflictFinder.findConflictingSimpleNames(simpleNames, onDemandImportedContainers, monitor);
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictingSimpleNameFinder.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictingSimpleNameFinder.java
new file mode 100644
index 0000000000..f81235edc5
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ConflictingSimpleNameFinder.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.JavaModelException;
+
+import java.util.Set;
+
+interface ConflictingSimpleNameFinder {
+ /**
+ * Finds duplicate declarations of the specified simple names within the specified on-demand and
+ * implicit import containers.
+ *
+ * @param simpleNames
+ * simple names of single imports in the compilation unit
+ * @param onDemandAndImplicitContainerNames
+ * names of on-demand and implicitly imported containers (e.g. "java.lang")
+ * @param monitor
+ * a progress monitor used to track time spent searching for conflicts
+ */
+ Set<String> findConflictingSimpleNames(
+ Set<String> simpleNames,
+ Set<String> onDemandAndImplicitContainerNames,
+ IProgressMonitor monitor) throws JavaModelException;
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportAdder.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportAdder.java
new file mode 100644
index 0000000000..9d258028e3
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportAdder.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.Collection;
+import java.util.List;
+
+interface ImportAdder {
+ /**
+ * Returns a new list containing the elements of {@code existingImports} and also containing
+ * each element of {@code importsToAdd} for which {@code existingImports} does not contain an
+ * equal element.
+ */
+ List<ImportName> addImports(Collection<ImportName> existingImports, Collection<ImportName> importsToAdd);
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComment.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComment.java
new file mode 100644
index 0000000000..2c7d35b116
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComment.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import org.eclipse.jface.text.IRegion;
+
+final class ImportComment {
+ /** The original location of this comment in the compilation unit. */
+ final IRegion region;
+
+ /**
+ * The number of line delimiters following this comment and preceding the next comment or the
+ * associated import declaration. Used to preserve blank lines between comments and/or import
+ * declarations. Will be 0 for a trailing comment.
+ */
+ final int succeedingLineDelimiters;
+
+ ImportComment(IRegion region, int succeedingLineDelims) {
+ this.region = region;
+ this.succeedingLineDelimiters = succeedingLineDelims;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComparator.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComparator.java
new file mode 100644
index 0000000000..d3288bc0d5
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportComparator.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.Comparator;
+
+/**
+ * Sorts imports by, in order of decreasing precedence, the following:
+ * <ul>
+ * <li>configured import group order</li>
+ * <li>package name and/or qualified name of containing type (lexicographically)</li>
+ * <li>qualified name of import (lexicographically)</li>
+ * </ul>
+ */
+final class ImportComparator implements Comparator<ImportName> {
+ private static Comparator<ImportName> createQualifiedNameComparator() {
+ return new Comparator<ImportName>() {
+ @Override
+ public int compare(ImportName o1, ImportName o2) {
+ return o1.qualifiedName.compareTo(o2.qualifiedName);
+ }
+ };
+ }
+
+ private final Comparator<ImportName> importGroupComparator;
+ private final Comparator<ImportName> typeContainerComparator;
+ private final Comparator<ImportName> staticContainerComparator;
+ private final Comparator<ImportName> qualifiedNameComparator;
+
+ ImportComparator(
+ ImportGroupComparator importGroupComparator,
+ Comparator<ImportName> typeContainerComparator,
+ Comparator<ImportName> staticContainerComparator) {
+ this.importGroupComparator = importGroupComparator;
+ this.typeContainerComparator = typeContainerComparator;
+ this.staticContainerComparator = staticContainerComparator;
+ this.qualifiedNameComparator = createQualifiedNameComparator();
+ }
+
+ @Override
+ public int compare(ImportName o1, ImportName o2) {
+ final int comparison;
+
+ int importGroupComparison = this.importGroupComparator.compare(o1, o2);
+ if (importGroupComparison != 0) {
+ comparison = importGroupComparison;
+ } else {
+ // The two imports sorted into the same import group, so o2.isStatic == o1.isStatic.
+ Comparator<ImportName> containerComparator =
+ o1.isStatic ? this.staticContainerComparator : this.typeContainerComparator;
+
+ int containerComparison = containerComparator.compare(o1, o2);
+ if (containerComparison != 0) {
+ comparison = containerComparison;
+ } else {
+ comparison = this.qualifiedNameComparator.compare(o1, o2);
+ }
+ }
+
+ return comparison;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java
new file mode 100644
index 0000000000..62383740d0
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+final class ImportDeclarationWriter {
+ private final boolean insertSpaceBeforeSemicolon;
+
+ ImportDeclarationWriter(boolean insertSpaceBeforeSemicolon) {
+ this.insertSpaceBeforeSemicolon = insertSpaceBeforeSemicolon;
+ }
+
+ /**
+ * Writes the Java source for an import declaration of the given name.
+ */
+ String writeImportDeclaration(ImportName importName) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("import "); //$NON-NLS-1$
+
+ if (importName.isStatic) {
+ sb.append("static "); //$NON-NLS-1$
+ }
+
+ sb.append(importName.qualifiedName);
+
+ if (this.insertSpaceBeforeSemicolon) {
+ sb.append(' ');
+ }
+
+ sb.append(';');
+
+ return sb.toString();
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEditor.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEditor.java
new file mode 100644
index 0000000000..144de49b11
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEditor.java
@@ -0,0 +1,536 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.text.edits.DeleteEdit;
+import org.eclipse.text.edits.InsertEdit;
+import org.eclipse.text.edits.MoveSourceEdit;
+import org.eclipse.text.edits.MoveTargetEdit;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.RangeMarker;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+
+/**
+ * Creates TextEdits to apply changes to the order of import declarations to a compilation unit.
+ */
+final class ImportEditor {
+ /**
+ * Iterates through the compilation unit's original import order, providing in turn each
+ * original import and the original start position of that import's leading delimiter.
+ */
+ private static final class OriginalImportsCursor {
+ private final Iterator<OriginalImportEntry> originalImportIterator;
+ OriginalImportEntry currentOriginalImport;
+ int currentPosition;
+
+ OriginalImportsCursor(int startPosition, Collection<OriginalImportEntry> originalImportEntries) {
+ this.originalImportIterator = originalImportEntries.iterator();
+ this.currentPosition = startPosition;
+ this.currentOriginalImport =
+ this.originalImportIterator.hasNext() ? this.originalImportIterator.next() : null;
+ }
+
+ /**
+ * Advances this cursor to the next import in the original order.
+ */
+ void advance() {
+ IRegion declarationAndComments = this.currentOriginalImport.declarationAndComments;
+ this.currentPosition = declarationAndComments.getOffset() + declarationAndComments.getLength();
+ this.currentOriginalImport =
+ this.originalImportIterator.hasNext() ? this.originalImportIterator.next() : null;
+ }
+ }
+
+ private static final class ImportEdits {
+ final Collection<TextEdit> leadingDelimiterEdits;
+ final Collection<TextEdit> commentAndDeclarationEdits;
+
+ ImportEdits(
+ Collection<TextEdit> leadingDelimiterEdits,
+ Collection<TextEdit> commentAndDeclarationEdits) {
+ this.leadingDelimiterEdits = leadingDelimiterEdits;
+ this.commentAndDeclarationEdits = commentAndDeclarationEdits;
+ }
+ }
+
+ /**
+ * Maps by identity each import (as key), except the last, to the import (as value) which comes
+ * before it.
+ * <p>
+ * Maps by identity (rather than by hashcode) to handle cases of duplicate import declarations.
+ */
+ private static Map<ImportName, ImportEntry> mapPrecedingImports(Collection<? extends ImportEntry> importEntries) {
+ Map<ImportName, ImportEntry> precedingImports =
+ new IdentityHashMap<ImportName, ImportEntry>(importEntries.size());
+
+ ImportEntry previousImport = null;
+ for (ImportEntry currentImport : importEntries) {
+ ImportName currentImportName = currentImport.importName;
+ precedingImports.put(currentImportName, previousImport);
+ previousImport = currentImport;
+ }
+
+ return precedingImports;
+ }
+
+ private static boolean containsFloatingComment(Iterable<ImportComment> comments) {
+ for (ImportComment comment : comments) {
+ if (comment.succeedingLineDelimiters > 1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private final String lineDelimiter;
+ private final String twoLineDelimiters;
+ private final boolean fixAllLineDelimiters;
+ private final int lineDelimitersBetweenImportGroups;
+ private final ImportGroupComparator importGroupComparator;
+ private final RemovedImportCommentReassigner commentReassigner;
+ private final Map<ImportName, ImportEntry> originalPrecedingImports;
+ private final List<OriginalImportEntry> originalImportEntries;
+ private final RewriteSite rewriteSite;
+ private final ImportDeclarationWriter declarationWriter;
+
+ /**
+ * @param lineDelimiter
+ * the string to use as a line delimiter when generating text edits
+ * @param fixAllLineDelimiters
+ * specifies whether to standardize whitespace between all imports (if true), or only
+ * between pairs of imports not originally subsequent (if false)
+ * @param lineDelimitersBetweenImportGroups
+ * the number of line delimiters desired between import declarations matching
+ * different import groups
+ * @param importGroupComparator
+ * used to determine whether two subsequent imports match the same import group
+ * @param originalImports
+ * the original order of imports in the compilation unit
+ * @param rewriteSite
+ * describes the location in the compilation unit where imports shall be rewriten
+ * @param importDeclarationWriter
+ * used to render each new import declaration (one not originally present in the
+ * compilation unit) as a string
+ */
+ ImportEditor(
+ String lineDelimiter,
+ boolean fixAllLineDelimiters,
+ int lineDelimitersBetweenImportGroups,
+ ImportGroupComparator importGroupComparator,
+ List<OriginalImportEntry> originalImports,
+ RewriteSite rewriteSite,
+ ImportDeclarationWriter importDeclarationWriter) {
+ this.lineDelimiter = lineDelimiter;
+ this.twoLineDelimiters = this.lineDelimiter.concat(this.lineDelimiter);
+ this.fixAllLineDelimiters = fixAllLineDelimiters;
+ this.lineDelimitersBetweenImportGroups = lineDelimitersBetweenImportGroups;
+ this.importGroupComparator = importGroupComparator;
+ this.originalImportEntries = originalImports;
+ this.rewriteSite = rewriteSite;
+ this.declarationWriter = importDeclarationWriter;
+
+ this.commentReassigner = new RemovedImportCommentReassigner(originalImports);
+
+ if (fixAllLineDelimiters) {
+ this.originalPrecedingImports = Collections.emptyMap();
+ } else {
+ this.originalPrecedingImports = Collections.unmodifiableMap(mapPrecedingImports(originalImports));
+ }
+ }
+
+ /**
+ * Generates and returns a TextEdit to replace or update the import declarations in the
+ * compilation unit to match the given list.
+ * <p>
+ * Standardizes whitespace between subsequent imports to the correct number of line delimiters
+ * (either for every pair of subsequent imports, or only for pairs not originally subsequent,
+ * depending on the value of {@link #fixAllLineDelimiters}).
+ * <p>
+ * Relocates leading and trailing comments of removed imports as determined by
+ * {@link #commentReassigner}.
+ */
+ TextEdit createTextEdit(Collection<ImportEntry> resultantImports) {
+ TextEdit edit = new MultiTextEdit();
+
+ IRegion surroundingRegion = this.rewriteSite.surroundingRegion;
+
+ if (resultantImports.isEmpty()) {
+ if (this.originalImportEntries.isEmpty()) {
+ // Leave the compilation unit as is.
+ }
+ else {
+ // Replace original imports and surrounding whitespace with enough line delimiters
+ // around preceding and/or succeeding elements.
+
+ String newWhitespace;
+ if (this.rewriteSite.hasPrecedingElements) {
+ int newDelims = this.rewriteSite.hasSucceedingElements ? 2 : 1;
+ newWhitespace = createDelimiter(newDelims);
+ } else {
+ newWhitespace = ""; //$NON-NLS-1$
+ }
+
+ edit.addChild(new ReplaceEdit(
+ surroundingRegion.getOffset(), surroundingRegion.getLength(), newWhitespace));
+ }
+ }
+ else {
+ if (this.originalImportEntries.isEmpty()) {
+ // Replace existing whitespace with preceding line delimiters, import declarations,
+ // and succeeding line delimiters.
+
+ Collection<TextEdit> importEdits = determineEditsForImports(
+ surroundingRegion, resultantImports);
+
+ if (this.rewriteSite.hasPrecedingElements) {
+ edit.addChild(new InsertEdit(surroundingRegion.getOffset(), createDelimiter(2)));
+ }
+
+ edit.addChildren(importEdits.toArray(new TextEdit[importEdits.size()]));
+
+ int newSucceedingDelims = this.rewriteSite.hasSucceedingElements ? 2 : 1;
+ String newSucceeding = createDelimiter(newSucceedingDelims);
+ edit.addChild(new InsertEdit(surroundingRegion.getOffset(), newSucceeding));
+ }
+ else {
+ // Replace original imports with new ones, leaving surrounding whitespace in place.
+
+ Collection<TextEdit> importEdits = determineEditsForImports(
+ this.rewriteSite.importsRegion, resultantImports);
+
+ edit.addChildren(importEdits.toArray(new TextEdit[importEdits.size()]));
+ }
+ }
+ return edit;
+ }
+
+ /**
+ * Concatenates the given number of line delimiters into a single string.
+ */
+ private String createDelimiter(int numberOfLineDelimiters) {
+ if (numberOfLineDelimiters < 1) {
+ throw new IllegalArgumentException();
+ }
+
+ if (numberOfLineDelimiters == 1) {
+ return this.lineDelimiter;
+ }
+
+ if (numberOfLineDelimiters == 2) {
+ return this.twoLineDelimiters;
+ }
+
+ StringBuilder correctDelimiter = new StringBuilder();
+ for (int i = 0; i < numberOfLineDelimiters; i++) {
+ correctDelimiter.append(this.lineDelimiter);
+ }
+ return correctDelimiter.toString();
+ }
+
+ private Collection<TextEdit> determineEditsForImports(
+ IRegion importsRegion,
+ Collection<ImportEntry> resultantImports) {
+ Collection<TextEdit> edits = new ArrayList<TextEdit>();
+
+ Map<ImportEntry, Collection<ImportComment>> commentReassignments =
+ this.commentReassigner.reassignComments(resultantImports);
+
+ OriginalImportsCursor cursor = new OriginalImportsCursor(
+ importsRegion.getOffset(), this.originalImportEntries);
+
+ edits.addAll(placeResultantImports(cursor, resultantImports, commentReassignments));
+
+ edits.addAll(deleteRemainingText(importsRegion, edits));
+
+ // Omit the RangeMarkers used temporarily to mark the text of non-relocated imports.
+ Collection<TextEdit> editsWithoutRangeMarkers = new ArrayList<TextEdit>(edits.size());
+ for (TextEdit edit : edits) {
+ if (!(edit instanceof RangeMarker)) {
+ editsWithoutRangeMarkers.add(edit);
+ }
+ }
+
+ return editsWithoutRangeMarkers;
+ }
+
+ /**
+ * Creates TextEdits that place each resultant import in the correct (rewritten) position.
+ */
+ private Collection<TextEdit> placeResultantImports(
+ OriginalImportsCursor cursor,
+ Collection<ImportEntry> resultantImports,
+ Map<ImportEntry, Collection<ImportComment>> commentReassignments) {
+ Collection<TextEdit> edits = new ArrayList<TextEdit>();
+
+ ImportEntry lastResultantImport = null;
+ for (ImportEntry currentResultantImport : resultantImports) {
+ if (currentResultantImport.isOriginal()) {
+ // Skip forward to this import's place in the original order.
+ while (cursor.currentOriginalImport != null
+ && cursor.currentOriginalImport != currentResultantImport) {
+ cursor.advance();
+ }
+ }
+
+ Collection<ImportComment> reassignedComments = commentReassignments.get(currentResultantImport);
+ if (reassignedComments == null) {
+ reassignedComments = Collections.emptyList();
+ }
+
+ ImportEdits importPlacement;
+ if (currentResultantImport.isOriginal()) {
+ OriginalImportEntry originalImport = currentResultantImport.asOriginalImportEntry();
+ if (cursor.currentOriginalImport == currentResultantImport) {
+ importPlacement = preserveStationaryImport(originalImport);
+ } else {
+ importPlacement = moveOriginalImport(originalImport, cursor.currentPosition);
+ }
+ } else {
+ importPlacement = placeNewImport(currentResultantImport, cursor.currentPosition);
+ }
+
+ String newDelimiter = determineNewDelimiter(
+ lastResultantImport, currentResultantImport, reassignedComments);
+ if (newDelimiter == null) {
+ edits.addAll(importPlacement.leadingDelimiterEdits);
+ } else if (!newDelimiter.isEmpty()) {
+ edits.add(new InsertEdit(cursor.currentPosition, newDelimiter));
+ }
+
+ if (!reassignedComments.isEmpty()) {
+ edits.addAll(relocateComments(reassignedComments, cursor.currentPosition));
+
+ boolean hasFloatingComment = currentResultantImport.isOriginal()
+ && containsFloatingComment(currentResultantImport.asOriginalImportEntry().comments);
+ String delimiterAfterReassignedComments =
+ hasFloatingComment ? this.twoLineDelimiters : this.lineDelimiter;
+ edits.add(new InsertEdit(cursor.currentPosition, delimiterAfterReassignedComments));
+ }
+
+ edits.addAll(importPlacement.commentAndDeclarationEdits);
+
+ if (currentResultantImport == cursor.currentOriginalImport) {
+ cursor.advance();
+ }
+
+ lastResultantImport = currentResultantImport;
+ }
+
+ return edits;
+ }
+
+ /**
+ * Creates text edits to insert the text of a new import.
+ */
+ private ImportEdits placeNewImport(ImportEntry currentResultantImport, int position) {
+ String declaration = this.declarationWriter.writeImportDeclaration(currentResultantImport.importName);
+ return new ImportEdits(
+ Collections.<TextEdit>emptySet(),
+ Collections.<TextEdit>singleton(new InsertEdit(position, declaration)));
+ }
+
+ /**
+ * Creates text edits to move an import's text to a new position.
+ */
+ private ImportEdits moveOriginalImport(OriginalImportEntry importEntry, int position) {
+ MoveSourceEdit leadingSourceEdit = new MoveSourceEdit(
+ importEntry.leadingDelimiter.getOffset(), importEntry.leadingDelimiter.getLength());
+ MoveTargetEdit leadingTargetEdit = new MoveTargetEdit(position, leadingSourceEdit);
+ Collection<TextEdit> leadingDelimiterEdits = Arrays.asList(leadingSourceEdit, leadingTargetEdit);
+
+ MoveSourceEdit importSourceEdit = new MoveSourceEdit(
+ importEntry.declarationAndComments.getOffset(), importEntry.declarationAndComments.getLength());
+ MoveTargetEdit importTargetEdit = new MoveTargetEdit(position, importSourceEdit);
+ Collection<TextEdit> declarationAndCommentEdits = Arrays.asList(importSourceEdit, importTargetEdit);
+
+ return new ImportEdits(leadingDelimiterEdits, declarationAndCommentEdits);
+ }
+
+ /**
+ * Creates RangeMarkers to mark a non-relocated import's text to prevent its deletion.
+ */
+ private ImportEdits preserveStationaryImport(OriginalImportEntry importEntry) {
+ return new ImportEdits(
+ Collections.<TextEdit>singleton(new RangeMarker(
+ importEntry.leadingDelimiter.getOffset(),
+ importEntry.leadingDelimiter.getLength())),
+ Collections.<TextEdit>singleton(new RangeMarker(
+ importEntry.declarationAndComments.getOffset(),
+ importEntry.declarationAndComments.getLength())));
+
+ }
+
+ /**
+ * Determines whether and how to standardize the whitespace between the end of the previous
+ * import (or its last trailing comment) and the start of the current import (or its first
+ * leading comment).
+ * <p>
+ * Returns a string containing the correct whitespace to place between the two imports, or
+ * {@code null} if the current import's original leading whitespace should be preserved.
+ */
+ private String determineNewDelimiter(
+ ImportEntry lastImport,
+ ImportEntry currentImport,
+ Collection<ImportComment> reassignedComments) {
+ if (lastImport == null) {
+ // The first import in the compilation unit needs no preceding line delimiters.
+ return ""; //$NON-NLS-1$
+ }
+
+ boolean hasReassignedComments = !reassignedComments.isEmpty();
+
+ if (!needsStandardDelimiter(lastImport, currentImport, hasReassignedComments)) {
+ return null;
+ }
+
+ int numberOfLineDelimiters = 1;
+
+ Collection<ImportComment> leadingComments;
+ if (hasReassignedComments) {
+ leadingComments = reassignedComments;
+ } else if (currentImport.isOriginal()) {
+ leadingComments = currentImport.asOriginalImportEntry().comments;
+ } else {
+ leadingComments = Collections.emptyList();
+ }
+ if (containsFloatingComment(leadingComments)) {
+ // Prevent a floating leading comment from becoming attached to the preceding import.
+ numberOfLineDelimiters = 2;
+ }
+
+ if (this.importGroupComparator.compare(lastImport.importName, currentImport.importName) != 0) {
+ // Separate imports belonging to different import groups.
+ numberOfLineDelimiters = Math.max(numberOfLineDelimiters, this.lineDelimitersBetweenImportGroups);
+ }
+
+ String standardDelimiter = createDelimiter(numberOfLineDelimiters);
+
+ // Reuse the original preceding delimiter if it matches the standard delimiter, but only
+ // if there are no reassigned comments (which would necessitate relocating the delimiter).
+ if (currentImport.isOriginal() && !hasReassignedComments) {
+ OriginalImportEntry originalImport = currentImport.asOriginalImportEntry();
+ IRegion originalDelimiter = originalImport.leadingDelimiter;
+ if (originalImport.precedingLineDelimiters == numberOfLineDelimiters) {
+ boolean delimiterIsSameLength = originalDelimiter == null && standardDelimiter.isEmpty()
+ || originalDelimiter != null && originalDelimiter.getLength() == standardDelimiter.length();
+ if (delimiterIsSameLength) {
+ return null;
+ }
+ }
+ }
+
+ return standardDelimiter;
+ }
+
+ /**
+ * Determines whether the whitespace between two subsequent imports should be set to a standard
+ * number of line delimiters.
+ */
+ private boolean needsStandardDelimiter(
+ ImportEntry lastImport,
+ ImportEntry currentImport,
+ boolean hasReassignedComments) {
+ boolean needsStandardDelimiter = false;
+
+ if (this.fixAllLineDelimiters) {
+ // In "Organize Imports" mode, all delimiters between imports are standardized.
+ needsStandardDelimiter = true;
+ } else if (!currentImport.isOriginal()) {
+ // This (new) import does not have an original leading delimiter.
+ needsStandardDelimiter = true;
+ } else if (hasReassignedComments) {
+ // Comments reassigned from removed imports are being prepended to this import.
+ needsStandardDelimiter = true;
+ } else {
+ ImportEntry originalPrecedingImport = this.originalPrecedingImports.get(currentImport.importName);
+ if (originalPrecedingImport == null || lastImport.importName != originalPrecedingImport.importName) {
+ // This import follows a different import post-rewrite than pre-rewrite.
+ needsStandardDelimiter = true;
+ }
+ }
+
+ return needsStandardDelimiter;
+ }
+
+ private Collection<TextEdit> relocateComments(Collection<ImportComment> reassignedComments, int insertPosition) {
+ if (reassignedComments.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Collection<TextEdit> edits = new ArrayList<TextEdit>(reassignedComments.size() * 3);
+
+ ImportComment lastComment = null;
+ for (ImportComment currentComment : reassignedComments) {
+ MoveSourceEdit sourceEdit = new MoveSourceEdit(
+ currentComment.region.getOffset(), currentComment.region.getLength());
+ edits.add(sourceEdit);
+
+ if (lastComment != null) {
+ // Preserve blank lines between comments.
+ int succeedingLineDelimiters = lastComment.succeedingLineDelimiters > 1 ? 2 : 1;
+
+ edits.add(new InsertEdit(insertPosition, createDelimiter(succeedingLineDelimiters)));
+ }
+
+ edits.add(new MoveTargetEdit(insertPosition, sourceEdit));
+
+ lastComment = currentComment;
+ }
+
+ return edits;
+ }
+
+ /**
+ * Creates TextEdits that delete text remaining between and after resultant imports.
+ */
+ private static Collection<TextEdit> deleteRemainingText(IRegion importRegion, Collection<TextEdit> edits) {
+ List<TextEdit> sortedEdits = new ArrayList<TextEdit>(edits);
+ Collections.sort(sortedEdits, new Comparator<TextEdit>() {
+ @Override
+ public int compare(TextEdit o1, TextEdit o2) {
+ return o1.getOffset() - o2.getOffset();
+ }
+ });
+
+ int deletePosition = importRegion.getOffset();
+
+ Collection<TextEdit> deleteRemainingTextEdits = new ArrayList<TextEdit>();
+ for (TextEdit edit : sortedEdits) {
+ if (edit.getOffset() > deletePosition) {
+ deleteRemainingTextEdits.add(new DeleteEdit(deletePosition, edit.getOffset() - deletePosition));
+ }
+
+ int editEndPosition = edit.getOffset() + edit.getLength();
+ deletePosition = Math.max(deletePosition, editEndPosition);
+ }
+
+ // Delete text remaining after the last import.
+ int importRegionEndPosition = importRegion.getOffset() + importRegion.getLength();
+ if (deletePosition < importRegionEndPosition) {
+ deleteRemainingTextEdits.add(new DeleteEdit(deletePosition, importRegionEndPosition - deletePosition));
+ }
+
+ return deleteRemainingTextEdits;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEntry.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEntry.java
new file mode 100644
index 0000000000..d93b86a6fa
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportEntry.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+/**
+ * Describes an import declaration, encapsulating the imported name and, for an import declaration
+ * originally present in the compilation unit, the regions originally occupied by the import
+ * declaration and its associated comments.
+ * <p>
+ * As the Java Language Specification allows duplicate import declarations, a compilation unit
+ * may contain multiple {@code ImportEntry}s with equal {@code ImportName}s.
+ */
+abstract class ImportEntry {
+ final ImportName importName;
+
+ protected ImportEntry(ImportName importName) {
+ this.importName = importName;
+ }
+
+ /**
+ * Returns true if this import declaration occurred originally (before the rewrite).
+ */
+ abstract boolean isOriginal();
+
+ /**
+ * If this import declaration occurred originally, returns it as an OriginalImportEntry;
+ * otherwise throws an exception.
+ */
+ abstract OriginalImportEntry asOriginalImportEntry();
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportGroupComparator.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportGroupComparator.java
new file mode 100644
index 0000000000..178dcc180d
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportGroupComparator.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+/**
+ * Sorts imports according to the order of import groups defined on the Organize Imports preference
+ * page. Considers equal any two imports matching the same import group.
+ */
+final class ImportGroupComparator implements Comparator<ImportName>{
+ private static final class ImportGroup {
+ private final String name;
+ private final int index;
+ private final ImportGroup prefix;
+
+ public ImportGroup(String name, int index, ImportGroup prefix) {
+ this.name = name;
+ this.index = index;
+ this.prefix = prefix;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ImportGroup(%d:%s)", getIndex(), getName()); //$NON-NLS-1$
+ }
+
+ String getName() {
+ return this.name;
+ }
+
+ int getIndex() {
+ return this.index;
+ }
+
+ ImportGroup getPrefix() {
+ return this.prefix;
+ }
+ }
+
+ private static final class IndexedImportGroups {
+ final NavigableMap<String, ImportGroup> typeImportGroupsByName;
+ final NavigableMap<String, ImportGroup> staticImportGroupByName;
+
+ IndexedImportGroups(
+ NavigableMap<String, ImportGroup> typeImportGroupsByName,
+ NavigableMap<String, ImportGroup> staticImportGroupsByName) {
+ this.typeImportGroupsByName = typeImportGroupsByName;
+ this.staticImportGroupByName = staticImportGroupsByName;
+ }
+ }
+
+ private static final String MATCH_ALL = ""; //$NON-NLS-1$
+ private static final String STATIC_PREFIX = "#"; //$NON-NLS-1$
+ private static final String STATIC_MATCH_ALL = STATIC_PREFIX + MATCH_ALL;
+
+ private static List<String> memoizedImportOrder = null;
+ private static IndexedImportGroups memoizedIndexedImportGroups = null;
+
+ private static List<String> includeMatchAllImportGroups(List<String> importOrder) {
+ boolean needsTypeMatchAll = !importOrder.contains(MATCH_ALL);
+ boolean needsStaticMatchAll = !importOrder.contains(STATIC_MATCH_ALL);
+
+ if (!needsTypeMatchAll && !needsStaticMatchAll) {
+ return importOrder;
+ }
+
+ List<String> augmentedOrder = new ArrayList<String>(importOrder.size() + 2);
+
+ if (needsStaticMatchAll) {
+ augmentedOrder.add(STATIC_MATCH_ALL);
+ }
+
+ augmentedOrder.addAll(importOrder);
+
+ if (needsTypeMatchAll) {
+ augmentedOrder.add(MATCH_ALL);
+ }
+
+ return augmentedOrder;
+ }
+
+ private static synchronized IndexedImportGroups indexImportOrder(List<String> importOrder) {
+ if (importOrder.equals(memoizedImportOrder)) {
+ return memoizedIndexedImportGroups;
+ }
+
+ Map<String, Integer> typeGroupsAndIndices = new HashMap<String, Integer>();
+ Map<String, Integer> staticGroupsAndIndices = new HashMap<String, Integer>();
+ for (int i = 0; i < importOrder.size(); i++) {
+ String importGroupString = importOrder.get(i);
+
+ final Map<String, Integer> groupsAndIndices;
+ if (importGroupString.startsWith(STATIC_PREFIX)) {
+ groupsAndIndices = staticGroupsAndIndices;
+ importGroupString = importGroupString.substring(1);
+ } else {
+ groupsAndIndices = typeGroupsAndIndices;
+ }
+
+ groupsAndIndices.put(importGroupString, i);
+ }
+
+ memoizedImportOrder = importOrder;
+
+ memoizedIndexedImportGroups = new IndexedImportGroups(
+ mapImportGroups(typeGroupsAndIndices),
+ mapImportGroups(staticGroupsAndIndices));
+
+ return memoizedIndexedImportGroups;
+ }
+
+ private static NavigableMap<String, ImportGroup> mapImportGroups(Map<String, Integer> importGroupNamesAndIndices) {
+ if (importGroupNamesAndIndices.isEmpty()) {
+ importGroupNamesAndIndices = Collections.singletonMap(MATCH_ALL, 0);
+ }
+
+ List<String> sortedNames = new ArrayList<String>(importGroupNamesAndIndices.keySet());
+ Collections.sort(sortedNames);
+
+ ArrayList<ImportGroup> importGroups = new ArrayList<ImportGroup>(sortedNames.size());
+
+ Deque<ImportGroup> prefixingGroups = new ArrayDeque<ImportGroup>();
+ for (String name : sortedNames) {
+ while (!prefixingGroups.isEmpty()
+ && !isWholeSegmentPrefix(prefixingGroups.getLast().getName(), name)) {
+ prefixingGroups.removeLast();
+ }
+ ImportGroup prefix = prefixingGroups.peekLast();
+
+ ImportGroup group = new ImportGroup(name, importGroupNamesAndIndices.get(name), prefix);
+
+ importGroups.add(group);
+
+ prefixingGroups.addLast(group);
+ }
+
+ NavigableMap<String, ImportGroup> groupsByName = new TreeMap<String, ImportGroup>();
+ for (ImportGroup group : importGroups) {
+ groupsByName.put(group.getName(), group);
+ }
+
+ return groupsByName;
+ }
+
+ private static boolean isWholeSegmentPrefix(String prefix, String name) {
+ if (!name.startsWith(prefix)) {
+ return false;
+ }
+
+ return prefix.isEmpty() || name.length() == prefix.length() || name.charAt(prefix.length()) == '.';
+ }
+
+ private final IndexedImportGroups indexedImportGroups;
+
+ ImportGroupComparator(List<String> importOrder) {
+ List<String> importOrderWithMatchAllGroups = includeMatchAllImportGroups(importOrder);
+ this.indexedImportGroups = indexImportOrder(importOrderWithMatchAllGroups);
+ }
+
+ @Override
+ public int compare(ImportName o1, ImportName o2) {
+ return determineSortPosition(o1) - determineSortPosition(o2);
+ }
+
+ private int determineSortPosition(ImportName importName) {
+ String name = (importName.isOnDemand() ? importName.containerName : importName.qualifiedName);
+
+ NavigableMap<String, ImportGroup> groupsByName = importName.isStatic
+ ? this.indexedImportGroups.staticImportGroupByName
+ : this.indexedImportGroups.typeImportGroupsByName;
+
+ ImportGroup prefixingGroup = groupsByName.floorEntry(name).getValue();
+ while (!isWholeSegmentPrefix(prefixingGroup.getName(), name)) {
+ prefixingGroup = prefixingGroup.getPrefix();
+ }
+
+ return prefixingGroup.getIndex();
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java
new file mode 100644
index 0000000000..2f432f0cbf
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.dom.ImportDeclaration;
+
+/**
+ * Encapsulates an import's fully qualified name, whether it is on-demand, and whether it is static.
+ * <p>
+ * The fully qualified name is divided into two parts:
+ * <ul>
+ * <li>a container name, which is everything preceding the last dot ('.').
+ * <li>a simple name, which is the part following the last dot ("*" for an on-demand import).
+ * </ul>
+ */
+public final class ImportName {
+ static ImportName createFor(ImportDeclaration importDeclaration) {
+ String declName = importDeclaration.getName().getFullyQualifiedName();
+ if (importDeclaration.isOnDemand()) {
+ return createOnDemand(importDeclaration.isStatic(), declName);
+ }
+ return createFor(importDeclaration.isStatic(), declName);
+ }
+
+ static ImportName createOnDemand(boolean isStatic, String containerName) {
+ return new ImportName(isStatic, containerName, "*"); //$NON-NLS-1$
+ }
+
+ public static ImportName createFor(boolean isStatic, String qualifiedName) {
+ String containerName = Signature.getQualifier(qualifiedName);
+ String simpleName = qualifiedName.substring(containerName.length() + 1);
+ return new ImportName(isStatic, containerName, simpleName);
+ }
+
+ public final boolean isStatic;
+ public final String containerName;
+ public final String simpleName;
+ public final String qualifiedName;
+
+ private ImportName(boolean isStatic, String containerName, String simpleName) {
+ this.isStatic = isStatic;
+ this.containerName = containerName;
+ this.simpleName = simpleName;
+
+ this.qualifiedName = this.containerName + "." + this.simpleName; //$NON-NLS-1$;
+ }
+
+ @Override
+ public String toString() {
+ String template = this.isStatic ? "staticImport(%s)" : "typeImport(%s)"; //$NON-NLS-1$ //$NON-NLS-2$
+ return String.format(template, this.qualifiedName);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = this.qualifiedName.hashCode();
+ result = 31 * result + (this.isStatic ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ImportName)) {
+ return false;
+ }
+
+ ImportName other = (ImportName) obj;
+
+ return this.qualifiedName.equals(other.qualifiedName) && this.isStatic == other.isStatic;
+ }
+
+ public boolean isOnDemand() {
+ return this.simpleName.equals("*"); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns an on-demand ImportName with the same isStatic and containerName as this ImportName.
+ */
+ ImportName getContainerOnDemand() {
+ if (this.isOnDemand()) {
+ return this;
+ }
+
+ return ImportName.createOnDemand(this.isStatic, this.containerName);
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java
new file mode 100644
index 0000000000..86f3c252a4
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java
@@ -0,0 +1,664 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2013, 2014, 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
+ * Stephan Herrmann - Contribution for Bug 378024 - Ordering of comments between imports not preserved
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+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.Comment;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ImportDeclaration;
+import org.eclipse.jdt.core.dom.PackageDeclaration;
+import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.dom.rewrite.imports.ConflictIdentifier.Conflicts;
+import org.eclipse.jdt.internal.core.util.Util;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.text.edits.TextEdit;
+
+/**
+ * Allows the caller to specify imports to be added to or removed from a compilation unit and
+ * creates a TextEdit which, applied to the compilation unit, effects the specified additions and
+ * removals.
+ * <p>
+ * Operates in either of two modes (selected via {@link ImportRewriteConfiguration.Builder}'s
+ * static factory methods):
+ * <ul>
+ * <li>Discarding original imports and totally sorting all imports added thereafter. This mode is
+ * used by the Organize Imports operation.</li>
+ * <li>Preserving original imports and placing each added import adjacent to the one most closely
+ * matching it. This mode is used e.g. when Content Assist adds an import for a completed name.</li>
+ * </ul>
+ */
+public final class ImportRewriteAnalyzer {
+ /**
+ * Encapsulates, for a computed import rewrite, a {@code TextEdit} that can be applied to effect
+ * the rewrite as well as the names of imports created by the rewrite.
+ */
+ public static final class RewriteResult {
+ private final TextEdit textEdit;
+ private final Set<ImportName> createdImports;
+
+ RewriteResult(TextEdit textEdit, Set<ImportName> createdImports) {
+ this.textEdit = textEdit;
+ this.createdImports = Collections.unmodifiableSet(createdImports);
+ }
+
+ /**
+ * Returns a {@link TextEdit} describing the changes necessary to perform the rewrite.
+ */
+ public TextEdit getTextEdit() {
+ return this.textEdit;
+ }
+
+ public String[] getCreatedImports() {
+ return extractQualifiedNames(false, this.createdImports);
+ }
+
+ public String[] getCreatedStaticImports() {
+ return extractQualifiedNames(true, this.createdImports);
+ }
+
+ private String[] extractQualifiedNames(boolean b, Collection<ImportName> imports) {
+ List<String> names = new ArrayList<String>(imports.size());
+ for (ImportName importName : imports) {
+ if (importName.isStatic == b) {
+ names.add(importName.qualifiedName);
+ }
+ }
+
+ return names.toArray(new String[names.size()]);
+ }
+ }
+
+ /**
+ * Returns the value of the formatter option specifying how many blank lines to insert between
+ * import groups.
+ */
+ private static int getBlankLinesBetweenImportGroups(IJavaProject javaProject) {
+ int num = -1;
+
+ String blankLinesOptionValue =
+ javaProject.getOption(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS, true);
+ try {
+ num = Integer.parseInt(blankLinesOptionValue);
+ } catch (NumberFormatException e) {
+ String message = String.format(
+ "Could not parse the value of %s as an integer: %s", //$NON-NLS-1$
+ DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS,
+ blankLinesOptionValue);
+ Util.log(new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, message, e));
+ }
+
+ return num >= 0 ? num : 1;
+ }
+
+ /**
+ * Returns the value of the formatter option specifying whether to insert a space between the
+ * imported name and the semicolon in an import declaration.
+ */
+ private static boolean shouldInsertSpaceBeforeSemicolon(IJavaProject javaProject) {
+ return JavaCore.INSERT.equals(
+ javaProject.getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_SEMICOLON, true));
+ }
+
+ /**
+ * Reads the positions of each existing import declaration along with any associated comments,
+ * and returns these in a list whose iteration order reflects the existing order of the imports
+ * in the compilation unit.
+ */
+ private static List<OriginalImportEntry> readOriginalImports(CompilationUnit compilationUnit) {
+ List<ImportDeclaration> importDeclarations = compilationUnit.imports();
+
+ if (importDeclarations.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<Comment> comments = compilationUnit.getCommentList();
+
+ int currentCommentIndex = 0;
+
+ // Skip over package and file header comments (see https://bugs.eclipse.org/121428).
+ ImportDeclaration firstImport = importDeclarations.get(0);
+ PackageDeclaration packageDeclaration = compilationUnit.getPackage();
+ int firstImportStartPosition = packageDeclaration == null
+ ? firstImport.getStartPosition()
+ : compilationUnit.getExtendedStartPosition(packageDeclaration)
+ + compilationUnit.getExtendedLength(packageDeclaration);
+ while (currentCommentIndex < comments.size()
+ && comments.get(currentCommentIndex).getStartPosition() < firstImportStartPosition) {
+ currentCommentIndex++;
+ }
+
+ List<OriginalImportEntry> imports = new ArrayList<OriginalImportEntry>(importDeclarations.size());
+ int previousExtendedEndPosition = -1;
+ for (ImportDeclaration currentImport : importDeclarations) {
+ int extendedEndPosition = compilationUnit.getExtendedStartPosition(currentImport)
+ + compilationUnit.getExtendedLength(currentImport);
+
+ int commentAfterImportIndex = currentCommentIndex;
+ while (commentAfterImportIndex < comments.size()
+ && comments.get(commentAfterImportIndex).getStartPosition() < extendedEndPosition) {
+ commentAfterImportIndex++;
+ }
+
+ List<ImportComment> importComments;
+ if (commentAfterImportIndex == currentCommentIndex) {
+ importComments = Collections.emptyList();
+ } else {
+ importComments = selectImportComments(
+ compilationUnit,
+ comments,
+ currentImport.getStartPosition(),
+ currentCommentIndex,
+ commentAfterImportIndex);
+ }
+
+ int importAndCommentsStartPosition = importComments.isEmpty()
+ ? currentImport.getStartPosition()
+ : Math.min(currentImport.getStartPosition(), importComments.get(0).region.getOffset());
+
+ IRegion leadingWhitespaceRegion;
+ int precedingLineDelimiters;
+ if (previousExtendedEndPosition == -1) {
+ leadingWhitespaceRegion = new Region(importAndCommentsStartPosition, 0);
+ precedingLineDelimiters = 0;
+ } else {
+ leadingWhitespaceRegion = new Region(
+ previousExtendedEndPosition, importAndCommentsStartPosition - previousExtendedEndPosition);
+ int importAndCommentsFirstLine = compilationUnit.getLineNumber(importAndCommentsStartPosition);
+ int lastLineOfPrevious = compilationUnit.getLineNumber(previousExtendedEndPosition - 1);
+ precedingLineDelimiters = importAndCommentsFirstLine - lastLineOfPrevious;
+ }
+ IRegion importAndCommentsRegion =
+ new Region(importAndCommentsStartPosition, extendedEndPosition - importAndCommentsStartPosition);
+
+ imports.add(new OriginalImportEntry(
+ ImportName.createFor(currentImport),
+ importComments,
+ precedingLineDelimiters,
+ leadingWhitespaceRegion,
+ importAndCommentsRegion));
+
+ currentCommentIndex = commentAfterImportIndex;
+ previousExtendedEndPosition = extendedEndPosition;
+ }
+
+ return imports;
+ }
+
+ private static List<ImportComment> selectImportComments(
+ CompilationUnit compilationUnit,
+ List<Comment> comments,
+ int importDeclarationStartPosition,
+ int commentStartIndex,
+ int commentEndIndex) {
+ List<ImportComment> importComments = new ArrayList<ImportComment>(comments.size());
+
+ Iterator<Comment> commentIterator = comments.subList(commentStartIndex, commentEndIndex).iterator();
+ Comment currentComment = commentIterator.hasNext() ? commentIterator.next() : null;
+ while (currentComment != null) {
+ int currentCommentStartPosition = currentComment.getStartPosition();
+ int currentCommentLength = currentComment.getLength();
+
+ Comment nextComment = commentIterator.hasNext() ? commentIterator.next() : null;
+
+ int succeedingLineDelims;
+ int nextCommentStartPosition = nextComment == null ? Integer.MAX_VALUE : nextComment.getStartPosition();
+ int nextStartPosition = Math.min(importDeclarationStartPosition, nextCommentStartPosition);
+ if (nextStartPosition == Integer.MAX_VALUE) {
+ // This trailing comment is located at the end of the import's extended range
+ // and we don't care how many line delimiters follow it.
+ succeedingLineDelims = 0;
+ } else {
+ int currentCommentEndLine =
+ compilationUnit.getLineNumber(currentCommentStartPosition + currentCommentLength);
+ int nextStartLine = compilationUnit.getLineNumber(nextStartPosition);
+ succeedingLineDelims = nextStartLine - currentCommentEndLine;
+ }
+
+ importComments.add(new ImportComment(
+ new Region(currentCommentStartPosition, currentCommentLength), succeedingLineDelims));
+
+ currentComment = nextComment;
+ }
+
+ return importComments;
+ }
+
+ private static RewriteSite determineRewriteSite(
+ CompilationUnit compilationUnit, List<OriginalImportEntry> originalImports) {
+ IRegion importsRegion = determineImportsRegion(originalImports);
+
+ IRegion surroundingRegion = determineSurroundingRegion(compilationUnit, importsRegion);
+
+ boolean hasPrecedingElements = surroundingRegion.getOffset() != 0;
+
+ boolean hasSucceedingElements =
+ surroundingRegion.getOffset() + surroundingRegion.getLength() != compilationUnit.getLength();
+
+ return new RewriteSite(
+ surroundingRegion,
+ importsRegion,
+ hasPrecedingElements,
+ hasSucceedingElements);
+ }
+
+ /**
+ * Determines the region originally occupied by imports and their associated comments.
+ * <p>
+ * Returns null if originalImports is null or empty.
+ */
+ private static IRegion determineImportsRegion(List<OriginalImportEntry> originalImports) {
+ if (originalImports == null || originalImports.isEmpty()) {
+ return null;
+ }
+
+ OriginalImportEntry firstImport = originalImports.get(0);
+ int start = firstImport.declarationAndComments.getOffset();
+
+ OriginalImportEntry lastImport = originalImports.get(originalImports.size() - 1);
+ int end = lastImport.declarationAndComments.getOffset()
+ + lastImport.declarationAndComments.getLength();
+
+ return new Region(start, end - start);
+ }
+
+ /**
+ * Determines the region to be occupied by imports, their associated comments, and surrounding
+ * whitespace.
+ */
+ private static IRegion determineSurroundingRegion(CompilationUnit compilationUnit, IRegion importsRegion) {
+ NavigableMap<Integer, ASTNode> nodesTreeMap = mapTopLevelNodes(compilationUnit);
+
+ int surroundingStart;
+ int positionAfterImports;
+ if (importsRegion == null) {
+ PackageDeclaration packageDeclaration = compilationUnit.getPackage();
+ if (packageDeclaration != null) {
+ surroundingStart = compilationUnit.getExtendedStartPosition(packageDeclaration)
+ + compilationUnit.getExtendedLength(packageDeclaration);
+ }
+ else {
+ surroundingStart = 0;
+ }
+
+ positionAfterImports = surroundingStart;
+ } else {
+ Entry<Integer, ASTNode> lowerEntry = nodesTreeMap.lowerEntry(importsRegion.getOffset());
+ if (lowerEntry != null) {
+ ASTNode precedingNode = lowerEntry.getValue();
+ surroundingStart = precedingNode.getStartPosition() + precedingNode.getLength();
+ } else {
+ surroundingStart = 0;
+ }
+
+ positionAfterImports = importsRegion.getOffset() + importsRegion.getLength();
+ }
+
+ Integer ceilingKey = nodesTreeMap.ceilingKey(positionAfterImports);
+ int surroundingEnd = ceilingKey != null ? ceilingKey : compilationUnit.getLength();
+
+ return new Region(surroundingStart, surroundingEnd - surroundingStart);
+ }
+
+ /**
+ * Builds a NavigableMap containing all of the given compilation unit's top-level nodes
+ * (package declaration, import declarations, type declarations, and non-doc comments),
+ * keyed by start position.
+ */
+ private static NavigableMap<Integer, ASTNode> mapTopLevelNodes(CompilationUnit compilationUnit) {
+ NavigableMap<Integer, ASTNode> map = new TreeMap<Integer, ASTNode>();
+
+ Collection<ASTNode> nodes = new ArrayList<ASTNode>();
+ if (compilationUnit.getPackage() != null) {
+ nodes.add(compilationUnit.getPackage());
+ }
+ nodes.addAll(compilationUnit.imports());
+ nodes.addAll(compilationUnit.types());
+ for (Comment comment : ((List<Comment>) compilationUnit.getCommentList())) {
+ // Include only top-level (non-doc) comments;
+ // doc comments are contained within their parent nodes' ranges.
+ if (comment.getParent() == null) {
+ nodes.add(comment);
+ }
+ }
+
+ for (ASTNode node : nodes) {
+ map.put(node.getStartPosition(), node);
+ }
+
+ return map;
+ }
+
+ /**
+ * Builds an {@code IdentityHashMap} having the elements of {@code imports} as values and each
+ * element's {@code importName} as corresponding key. This map can be used to recall the {@code
+ * ImportEntry} corresponding to a given {@code ImportName} instance even when there are
+ * duplicate import declarations (where multiple {@code ImportEntry}s have equal, but not
+ * identical, {@code ImportName}s).
+ */
+ private static Map<ImportName, OriginalImportEntry> mapImportsByNameIdentity(List<OriginalImportEntry> imports) {
+ Map<ImportName, OriginalImportEntry> importsByName = new IdentityHashMap<ImportName, OriginalImportEntry>();
+
+ for (OriginalImportEntry currentImport : imports) {
+ importsByName.put(currentImport.importName, currentImport);
+ }
+
+ return Collections.unmodifiableMap(importsByName);
+ }
+
+ /**
+ * Returns a new {@code List} containing those elements of {@code imports} (in their existing
+ * order) not contained in {@code importsToSubtract}.
+ */
+ private static List<ImportName> subtractImports(
+ Collection<ImportName> existingImports, Set<ImportName> importsToSubtract) {
+ List<ImportName> remainingImports = new ArrayList<ImportName>(existingImports.size());
+ for (ImportName existingImport : existingImports) {
+ if (!importsToSubtract.contains(existingImport)) {
+ remainingImports.add(existingImport);
+ }
+ }
+ return remainingImports;
+ }
+
+ private final List<OriginalImportEntry> originalImportEntries;
+ private final List<ImportName> originalImportsList;
+ private final Set<ImportName> originalImportsSet;
+
+ private final ImportDeclarationWriter importDeclarationWriter;
+
+ private final ImportAdder importAdder;
+
+ private final Set<ImportName> importsToAdd;
+ private final Set<ImportName> importsToRemove;
+
+ private final boolean reportAllResultantImportsAsCreated;
+
+ private final Set<String> typeExplicitSimpleNames;
+ private final Set<String> staticExplicitSimpleNames;
+
+ private final Set<String> implicitImportContainerNames;
+
+ private final ConflictIdentifier conflictIdentifier;
+
+ private final OnDemandComputer onDemandComputer;
+
+ private final Map<ImportName, OriginalImportEntry> importsByNameIdentity;
+
+ private final String lineDelimiter;
+
+ private final ImportEditor importEditor;
+
+ public ImportRewriteAnalyzer(
+ ICompilationUnit cu,
+ CompilationUnit astRoot,
+ ImportRewriteConfiguration configuration) throws JavaModelException {
+ this.originalImportEntries = Collections.unmodifiableList(readOriginalImports(astRoot));
+
+ List<ImportName> importsList = new ArrayList<ImportName>(this.originalImportEntries.size());
+ Set<ImportName> importsSet = new HashSet<ImportName>();
+ for (ImportEntry originalImportEntry : this.originalImportEntries) {
+ ImportName importName = originalImportEntry.importName;
+ importsList.add(importName);
+ importsSet.add(importName);
+ }
+ this.originalImportsList = Collections.unmodifiableList(importsList);
+ this.originalImportsSet = Collections.unmodifiableSet(importsSet);
+
+ this.importsToAdd = new LinkedHashSet<ImportName>();
+
+ this.importsToRemove = new LinkedHashSet<ImportName>();
+ if (configuration.originalImportHandling.shouldRemoveOriginalImports()) {
+ this.importsToRemove.addAll(importsSet);
+ this.reportAllResultantImportsAsCreated = true;
+ } else {
+ this.reportAllResultantImportsAsCreated = false;
+ }
+
+ this.typeExplicitSimpleNames = new HashSet<String>();
+ this.staticExplicitSimpleNames = new HashSet<String>();
+
+ ImportGroupComparator importGroupComparator = new ImportGroupComparator(configuration.importOrder);
+
+ JavaProject javaProject = (JavaProject) cu.getJavaProject();
+
+ this.importAdder = configuration.originalImportHandling.createImportAdder(new ImportComparator(
+ importGroupComparator,
+ configuration.typeContainerSorting.createContainerComparator(javaProject),
+ configuration.staticContainerSorting.createContainerComparator(javaProject)));
+
+ this.implicitImportContainerNames =
+ configuration.implicitImportIdentification.determineImplicitImportContainers(cu);
+
+ this.onDemandComputer = new OnDemandComputer(
+ configuration.typeOnDemandThreshold,
+ configuration.staticOnDemandThreshold);
+
+ this.conflictIdentifier = new ConflictIdentifier(
+ this.onDemandComputer,
+ new TypeConflictingSimpleNameFinder(javaProject, new SearchEngine()),
+ new StaticConflictingSimpleNameFinder(javaProject),
+ this.implicitImportContainerNames);
+
+ this.importsByNameIdentity = mapImportsByNameIdentity(this.originalImportEntries);
+
+ this.importDeclarationWriter = new ImportDeclarationWriter(shouldInsertSpaceBeforeSemicolon(javaProject));
+
+ this.lineDelimiter = cu.findRecommendedLineSeparator();
+
+ this.importEditor = new ImportEditor(
+ this.lineDelimiter,
+ configuration.originalImportHandling.shouldFixAllLineDelimiters(),
+ getBlankLinesBetweenImportGroups(javaProject) + 1,
+ importGroupComparator,
+ this.originalImportEntries,
+ determineRewriteSite(astRoot, this.originalImportEntries),
+ this.importDeclarationWriter);
+ }
+
+ /**
+ * Specifies that applying the rewrite should result in the compilation unit containing the
+ * specified import.
+ * <p>
+ * Has no effect if the compilation unit otherwise would contain the given import.
+ * <p>
+ * Overrides any previous corresponding call to {@link #removeImport}.
+ */
+ public void addImport(boolean isStatic, String qualifiedName) {
+ ImportName importToAdd = ImportName.createFor(isStatic, qualifiedName);
+ this.importsToAdd.add(importToAdd);
+ this.importsToRemove.remove(importToAdd);
+ }
+
+ /**
+ * Specifies that applying the rewrite should result in the compilation unit not containing the
+ * specified import.
+ * <p>
+ * Has no effect if the compilation unit otherwise would not contain the given import.
+ * <p>
+ * Overrides any previous corresponding call to {@link #addImport}.
+ */
+ public void removeImport(boolean isStatic, String qualifiedName) {
+ ImportName importToRemove = ImportName.createFor(isStatic, qualifiedName);
+ this.importsToAdd.remove(importToRemove);
+ this.importsToRemove.add(importToRemove);
+ }
+
+ /**
+ * Specifies that any import of the given simple name must be explicit - that it may neither be
+ * reduced into an on-demand (".*") import nor be filtered as an implicit (e.g. "java.lang.*")
+ * import.
+ */
+ public void requireExplicitImport(boolean isStatic, String simpleName) {
+ if (isStatic) {
+ this.staticExplicitSimpleNames.add(simpleName);
+ } else {
+ this.typeExplicitSimpleNames.add(simpleName);
+ }
+ }
+
+ /**
+ * Computes and returns the result of performing the rewrite, incorporating all changes
+ * specified by calls to {@link #addImport}, {@link #removeImport}, and
+ * {@link #requireExplicitImport}.
+ * <p>
+ * This method has no side-effects.
+ */
+ public RewriteResult analyzeRewrite(IProgressMonitor monitor) throws JavaModelException {
+ List<ImportName> computedImportOrder = computeImportOrder(monitor);
+
+ List<ImportEntry> resultingImportEntries = matchExistingOrCreateNew(computedImportOrder);
+
+ TextEdit edit = this.importEditor.createTextEdit(resultingImportEntries);
+
+ Set<ImportName> createdImports = new HashSet<ImportName>(computedImportOrder);
+ if (!this.reportAllResultantImportsAsCreated) {
+ createdImports.removeAll(this.originalImportsSet);
+ }
+
+ return new RewriteResult(edit, createdImports);
+ }
+
+ private List<ImportName> computeImportOrder(IProgressMonitor progressMonitor) throws JavaModelException {
+ Set<ImportName> importsWithAdditionsAndRemovals = new HashSet<ImportName>(this.originalImportsSet);
+ importsWithAdditionsAndRemovals.addAll(this.importsToAdd);
+ importsWithAdditionsAndRemovals.removeAll(this.importsToRemove);
+
+ Set<ImportName> touchedContainers = determineTouchedContainers();
+
+ Conflicts conflicts = this.conflictIdentifier.identifyConflicts(
+ importsWithAdditionsAndRemovals,
+ touchedContainers,
+ this.typeExplicitSimpleNames,
+ this.staticExplicitSimpleNames,
+ progressMonitor);
+
+ Set<String> allTypeExplicitSimpleNames = new HashSet<String>(this.typeExplicitSimpleNames);
+ allTypeExplicitSimpleNames.addAll(conflicts.typeConflicts);
+
+ Set<String> allStaticExplicitSimpleNames = new HashSet<String>(this.staticExplicitSimpleNames);
+ allStaticExplicitSimpleNames.addAll(conflicts.staticConflicts);
+
+ Set<ImportName> implicitImports = identifyImplicitImports(this.importsToAdd, allTypeExplicitSimpleNames);
+ List<ImportName> importsWithoutImplicits =
+ subtractImports(importsWithAdditionsAndRemovals, implicitImports);
+
+ Collection<OnDemandReduction> onDemandReductions = this.onDemandComputer.identifyPossibleReductions(
+ new HashSet<ImportName>(importsWithoutImplicits),
+ touchedContainers,
+ allTypeExplicitSimpleNames,
+ allStaticExplicitSimpleNames);
+
+ ImportsDelta delta = computeDelta(implicitImports, onDemandReductions);
+
+ List<ImportName> importsWithRemovals = subtractImports(this.originalImportsList, delta.importsToRemove);
+
+ List<ImportName> importsWithAdditions = this.importAdder.addImports(importsWithRemovals, delta.importsToAdd);
+
+ return importsWithAdditions;
+ }
+
+ private Set<ImportName> determineTouchedContainers() {
+ Collection<ImportName> touchedContainers = new ArrayList<ImportName>(
+ this.importsToAdd.size() + this.importsToRemove.size());
+
+ for (ImportName addedImport : this.importsToAdd) {
+ touchedContainers.add(addedImport.getContainerOnDemand());
+ }
+ for (ImportName removedImport : this.importsToRemove) {
+ touchedContainers.add(removedImport.getContainerOnDemand());
+ }
+
+ return Collections.unmodifiableSet(new HashSet<ImportName>(touchedContainers));
+ }
+
+ private Set<ImportName> identifyImplicitImports(
+ Collection<ImportName> addedImports, Set<String> allTypeExplicitSimpleNames) {
+ if (this.implicitImportContainerNames.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Collection<ImportName> implicits = new ArrayList<ImportName>(addedImports.size());
+ for (ImportName addedImport : addedImports) {
+ boolean isImplicit = this.implicitImportContainerNames.contains(addedImport.containerName)
+ && !allTypeExplicitSimpleNames.contains(addedImport.simpleName);
+ if (isImplicit) {
+ implicits.add(addedImport);
+ }
+ }
+
+ if (implicits.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ return new HashSet<ImportName>(implicits);
+ }
+
+ private List<ImportEntry> matchExistingOrCreateNew(Collection<ImportName> importNames) {
+ List<ImportEntry> importEntries = new ArrayList<ImportEntry>(importNames.size());
+ for (ImportName importName : importNames) {
+ ImportEntry importEntry = this.importsByNameIdentity.get(importName);
+
+ if (importEntry == null) {
+ importEntry = new NewImportEntry(importName);
+ }
+
+ importEntries.add(importEntry);
+ }
+ return importEntries;
+ }
+
+ private ImportsDelta computeDelta(
+ Collection<ImportName> implicitImports, Collection<OnDemandReduction> onDemandReductions) {
+ Collection<ImportName> additions = new ArrayList<ImportName>(this.originalImportsList.size());
+ additions.addAll(this.importsToAdd);
+
+ Collection<ImportName> removals = new ArrayList<ImportName>(this.originalImportsList.size());
+ removals.addAll(this.importsToRemove);
+ removals.addAll(implicitImports);
+
+ additions.removeAll(removals);
+
+ for (OnDemandReduction onDemandReduction : onDemandReductions) {
+ additions.removeAll(onDemandReduction.reducibleImports);
+ removals.addAll(onDemandReduction.reducibleImports);
+
+ additions.add(onDemandReduction.containerOnDemand);
+ removals.remove(onDemandReduction.containerOnDemand);
+ }
+
+ return new ImportsDelta(additions, removals);
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteConfiguration.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteConfiguration.java
new file mode 100644
index 0000000000..c77fee8582
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteConfiguration.java
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.internal.core.JavaProject;
+
+/**
+ * Used as a constructor parameter to ImportRewriteAnalyzer to configure its behavior.
+ * <p>
+ * The starting points are the two static factory methods of {@link Builder}.
+ */
+public final class ImportRewriteConfiguration {
+ public enum OriginalImportHandling {
+ /**
+ * Specifies to discard original imports and totally sort all new imports, as in the case of
+ * the "Organize Imports" operation.
+ */
+ DISCARD {
+ @Override
+ boolean shouldRemoveOriginalImports() {
+ return true;
+ }
+
+ @Override
+ boolean shouldFixAllLineDelimiters() {
+ return true;
+ }
+
+ @Override
+ ImportAdder createImportAdder(Comparator<ImportName> importComparator) {
+ return new ReorderingImportAdder(importComparator);
+ }
+ },
+ /**
+ * Specifies to keep original imports in their original order, placing each newly added
+ * import adjacent to the original import that it most closely matches.
+ */
+ PRESERVE_IN_ORDER {
+ @Override
+ boolean shouldRemoveOriginalImports() {
+ return false;
+ }
+
+ @Override
+ boolean shouldFixAllLineDelimiters() {
+ return false;
+ }
+
+ @Override
+ ImportAdder createImportAdder(Comparator<ImportName> importComparator) {
+ return new OrderPreservingImportAdder(importComparator);
+ }
+ },
+ ;
+
+ /**
+ * If true, ImportRewriteAnalyzer will, during its initialization, mark all original imports
+ * for removal.
+ */
+ abstract boolean shouldRemoveOriginalImports();
+
+ /**
+ * If true, line delimiters will be standardized between every pair of adjacent imports.
+ * Otherwise, line delimiters will be corrected only between pairs of adjacent imports that
+ * were not adjacent originally.
+ */
+ abstract boolean shouldFixAllLineDelimiters();
+
+ /**
+ * Creates the {@link ImportAdder} which will combine and order new and existing imports
+ * together.
+ */
+ abstract ImportAdder createImportAdder(Comparator<ImportName> importComparator);
+ }
+
+ /**
+ * Specifies how to sort import declarations by their packages and/or containing types.
+ */
+ public enum ImportContainerSorting {
+ /**
+ * Sorts imports by each import's package and any containing types, in lexicographic order.
+ * For example (assuming that all of the imports belong to the same import group):
+ * <pre>
+ * import java.net.Socket;
+ * import java.util.Map;
+ * import java.util.Set;
+ * import java.util.Map.Entry;
+ * </pre>
+ */
+ BY_PACKAGE_AND_CONTAINING_TYPE {
+ @Override
+ Comparator<ImportName> createContainerComparator(JavaProject javaProject) {
+ return new PackageAndContainingTypeImportComparator();
+ }
+ },
+
+ /**
+ * Sorts imports by each import's package, in lexicographic order. For example (assuming all
+ * of the imports belong to the same import group):
+ * <pre>
+ * import java.net.Socket;
+ * import java.util.Map;
+ * import java.util.Map.Entry;
+ * import java.util.Set;
+ * </pre>
+ */
+ BY_PACKAGE {
+ @Override
+ Comparator<ImportName> createContainerComparator(JavaProject javaProject) {
+ return new PackageImportComparator(javaProject);
+ }
+ },
+ ;
+
+ abstract Comparator<ImportName> createContainerComparator(JavaProject javaProject);
+ }
+
+ /**
+ * Specifies which types are considered to be implicitly imported.
+ * <p>
+ * An import declaration of such a type will not be added to the compilation unit unless it is
+ * needed to resolve a conflict with an on-demand imports, or the type's simple name has been
+ * specified with {@link ImportRewriteAnalyzer#requireExplicitImport}.
+ * <p>
+ * Also, implicitly imported types will be considered for conflicts when deciding which types
+ * from other packages can be reduced into on-demand imports. E.g. if java.lang.Integer were
+ * considered to be implicitly imported, that would prevent an import of com.example.Integer
+ * from being reduced into an on-demand import of com.example.*.
+ */
+ public enum ImplicitImportIdentification {
+ /**
+ * Specifies that types from the following packages are considered to be implicitly
+ * imported:
+ * <ul>
+ * <li>java.lang</li>
+ * <li>the package of the compilation unit being rewritten</li>
+ * </ul>
+ */
+ JAVA_LANG_AND_CU_PACKAGE {
+ @Override
+ Set<String> determineImplicitImportContainers(ICompilationUnit compilationUnit) {
+ Set<String> implicitImportContainerNames = new HashSet<String>();
+
+ implicitImportContainerNames.add("java.lang"); //$NON-NLS-1$
+
+ String compilationUnitPackageName = compilationUnit.getParent().getElementName();
+ implicitImportContainerNames.add(compilationUnitPackageName);
+
+ return implicitImportContainerNames;
+ }
+ },
+ /**
+ * Specifies that no types are considered to be implicitly imported.
+ */
+ NONE {
+ @Override
+ Set<String> determineImplicitImportContainers(ICompilationUnit compilationUnit) {
+ return Collections.emptySet();
+ }
+ },
+ ;
+
+ abstract Set<String> determineImplicitImportContainers(ICompilationUnit compilationUnit);
+ }
+
+ public static class Builder {
+ public static Builder discardingOriginalImports() {
+ return new Builder(OriginalImportHandling.DISCARD);
+ }
+
+ public static Builder preservingOriginalImports() {
+ return new Builder(OriginalImportHandling.PRESERVE_IN_ORDER);
+ }
+
+ final OriginalImportHandling originalImportHandling;
+ ImportContainerSorting typeContainerSorting;
+ ImportContainerSorting staticContainerSorting;
+ ImplicitImportIdentification implicitImportIdentification;
+ List<String> importOrder;
+ Integer typeOnDemandThreshold;
+ Integer staticOnDemandThreshold;
+
+ private Builder(OriginalImportHandling originalImportHandling) {
+ this.originalImportHandling = originalImportHandling;
+ this.typeContainerSorting = ImportContainerSorting.BY_PACKAGE;
+ this.staticContainerSorting = ImportContainerSorting.BY_PACKAGE_AND_CONTAINING_TYPE;
+ this.implicitImportIdentification = ImplicitImportIdentification.JAVA_LANG_AND_CU_PACKAGE;
+ this.importOrder = Collections.emptyList();
+ this.typeOnDemandThreshold = null;
+ this.staticOnDemandThreshold = null;
+ }
+
+ public Builder setTypeContainerSorting(ImportContainerSorting typeContainerSorting) {
+ this.typeContainerSorting = typeContainerSorting;
+ return this;
+ }
+
+ public Builder setStaticContainerSorting(ImportContainerSorting staticContainerSorting) {
+ this.staticContainerSorting = staticContainerSorting;
+ return this;
+ }
+
+ public Builder setImplicitImportIdentification(ImplicitImportIdentification implicitImportIdentification) {
+ this.implicitImportIdentification = implicitImportIdentification;
+ return this;
+ }
+
+ public Builder setImportOrder(List<String> importOrder) {
+ this.importOrder = Collections.unmodifiableList(new ArrayList<String>(importOrder));
+ return this;
+ }
+
+ public Builder setTypeOnDemandThreshold(int typeOnDemandThreshold) {
+ this.typeOnDemandThreshold = typeOnDemandThreshold;
+ return this;
+ }
+
+ public Builder setStaticOnDemandThreshold(int staticOnDemandThreshold) {
+ this.staticOnDemandThreshold = staticOnDemandThreshold;
+ return this;
+ }
+
+ public ImportRewriteConfiguration build() {
+ return new ImportRewriteConfiguration(this);
+ }
+ }
+
+ final OriginalImportHandling originalImportHandling;
+ final ImportContainerSorting typeContainerSorting;
+ final ImportContainerSorting staticContainerSorting;
+ final ImplicitImportIdentification implicitImportIdentification;
+ final List<String> importOrder;
+ final int typeOnDemandThreshold;
+ final int staticOnDemandThreshold;
+
+ ImportRewriteConfiguration(Builder builder) {
+ this.originalImportHandling = builder.originalImportHandling;
+ this.typeContainerSorting = builder.typeContainerSorting;
+ this.staticContainerSorting = builder.staticContainerSorting;
+ this.implicitImportIdentification = builder.implicitImportIdentification;
+ this.importOrder = builder.importOrder;
+ this.typeOnDemandThreshold = builder.typeOnDemandThreshold;
+ this.staticOnDemandThreshold = builder.staticOnDemandThreshold;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportsDelta.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportsDelta.java
new file mode 100644
index 0000000000..a630417ea4
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportsDelta.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Encapsulates a set of imports to add and a set of imports to remove.
+ */
+final class ImportsDelta {
+ final Set<ImportName> importsToAdd;
+ final Set<ImportName> importsToRemove;
+
+ ImportsDelta(Collection<ImportName> importsToAdd, Collection<ImportName> importsToRemove) {
+ this.importsToAdd = Collections.unmodifiableSet(new HashSet<ImportName>(importsToAdd));
+ this.importsToRemove = Collections.unmodifiableSet(new HashSet<ImportName>(importsToRemove));
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "(additions: %s, removals: %s)", //$NON-NLS-1$
+ this.importsToAdd,
+ this.importsToRemove);
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/NewImportEntry.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/NewImportEntry.java
new file mode 100644
index 0000000000..8a5a9292b1
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/NewImportEntry.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+/**
+ * Represents an import declaration that did not originally occur in the compilation unit.
+ */
+class NewImportEntry extends ImportEntry {
+ NewImportEntry(ImportName importName) {
+ super(importName);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("NewImportEntry(%s)", this.importName); //$NON-NLS-1$
+ }
+
+ @Override
+ boolean isOriginal() {
+ return false;
+ }
+
+ @Override
+ OriginalImportEntry asOriginalImportEntry() {
+ throw new UnsupportedOperationException();
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java
new file mode 100644
index 0000000000..71d8f1f76f
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+class OnDemandComputer {
+ private int typeOnDemandThreshold;
+ private int staticOnDemandThreshold;
+
+ OnDemandComputer(int typeOnDemandThreshold, int staticOnDemandThreshold) {
+ this.typeOnDemandThreshold = typeOnDemandThreshold;
+ this.staticOnDemandThreshold = staticOnDemandThreshold;
+ }
+
+ /**
+ * Identifies on-demand reductions (additions of on-demand imports with corresponding removal of
+ * single imports) satisfying the type and static on-demand import thresholds.
+ * <p>
+ * Only the containers imports which have been added or removed as part of the rewrite
+ * will be considered for on-demand reductions.
+ *
+ * @param imports
+ * the imports in the compilation unit
+ * @param touchedContainers
+ * the containers of all imports being added or removed as part of this rewrite,
+ * which should be considered for on-demand reductions (specified as an on-demand
+ * ImportName for each container)
+ * @param typeExplicitSimpleNames
+ * simple names of non-static single imports which must be preserved as single
+ * imports and not reduced into on-demand imports
+ * @param staticExplicitSimpleNames
+ * simple names of static single imports which must be preserved as single
+ * imports and not reduced into on-demand imports
+ */
+ Collection<OnDemandReduction> identifyPossibleReductions(
+ Set<ImportName> imports,
+ Set<ImportName> touchedContainers,
+ Set<String> typeExplicitSimpleNames,
+ Set<String> staticExplicitSimpleNames) {
+ Collection<OnDemandReduction> candidates = new ArrayList<OnDemandReduction>();
+
+ Map<ImportName, Collection<ImportName>> importsByContainer = mapByContainer(imports);
+
+ for (Map.Entry<ImportName, Collection<ImportName>> containerAndImports : importsByContainer.entrySet()) {
+ ImportName containerOnDemand = containerAndImports.getKey();
+ Collection<ImportName> containerImports = containerAndImports.getValue();
+
+ Set<String> explicitSimpleNames =
+ containerOnDemand.isStatic ? staticExplicitSimpleNames : typeExplicitSimpleNames;
+
+ int onDemandThreshold =
+ containerOnDemand.isStatic ? this.staticOnDemandThreshold : this.typeOnDemandThreshold;
+
+ if (touchedContainers.contains(containerOnDemand)) {
+ OnDemandReduction candidate = maybeReduce(
+ containerOnDemand, containerImports, onDemandThreshold, explicitSimpleNames);
+ if (candidate != null) {
+ candidates.add(candidate);
+ }
+ }
+ }
+
+ return candidates;
+ }
+
+ private Map<ImportName, Collection<ImportName>> mapByContainer(Collection<ImportName> imports) {
+ Map<ImportName, Collection<ImportName>> importsByContainer = new HashMap<ImportName, Collection<ImportName>>();
+ for (ImportName importName : imports) {
+ ImportName containerOnDemand = importName.getContainerOnDemand();
+
+ Collection<ImportName> containerImports = importsByContainer.get(containerOnDemand);
+ if (containerImports == null) {
+ containerImports = new ArrayList<ImportName>();
+ importsByContainer.put(containerOnDemand, containerImports);
+ }
+
+ containerImports.add(importName);
+ }
+
+ return importsByContainer;
+ }
+
+ private OnDemandReduction maybeReduce(
+ ImportName containerOnDemand,
+ Collection<ImportName> containerImports,
+ int onDemandThreshold,
+ Set<String> explicitSimpleNames) {
+ boolean containerHasOnDemand = false;
+ Collection<ImportName> reducibleImports = new ArrayList<ImportName>();
+
+ for (ImportName currentImport : containerImports) {
+ if (currentImport.isOnDemand()) {
+ containerHasOnDemand = true;
+ } else {
+ if (!explicitSimpleNames.contains(currentImport.simpleName)) {
+ reducibleImports.add(currentImport);
+ }
+ }
+ }
+
+ if (containerHasOnDemand || reducibleImports.size() >= onDemandThreshold) {
+ return new OnDemandReduction(containerOnDemand, reducibleImports);
+ }
+
+ return null;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandReduction.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandReduction.java
new file mode 100644
index 0000000000..9be51f6c92
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandReduction.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Indicates that one or more single imports ({@code reducibleImports}) are unnecessary or could be
+ * rendered unnecessary by the presence of an on-demand (.*) import ({@code containerOnDemand}) of
+ * their containing type or package.
+ * <p>
+ * This "reduction" can be applied by removing all declarations of {@code reducibleImports}
+ * from the compilation unit and adding a declaration of {@code containerOnDemand} to the
+ * compilation unit if one is not already present.
+ */
+class OnDemandReduction {
+ final ImportName containerOnDemand;
+ final Collection<ImportName> reducibleImports;
+
+ OnDemandReduction(ImportName containerName, Collection<ImportName> reducibleImports) {
+ this.containerOnDemand = containerName;
+ this.reducibleImports = Collections.unmodifiableCollection(new ArrayList<ImportName>(reducibleImports));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s: %s}", this.containerOnDemand.containerName, this.reducibleImports); //$NON-NLS-1$
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OrderPreservingImportAdder.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OrderPreservingImportAdder.java
new file mode 100644
index 0000000000..398652eb5b
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OrderPreservingImportAdder.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeSet;
+
+/**
+ * Keeping existing imports in their existing order, inserts each new import before or after the
+ * import to which it would be adjacent if all (existing and new) imports were totally ordered
+ * together.
+ * <p>
+ * A new import that would sort between two existing imports which are not adjacent in the
+ * existing order will be placed adjacent to the existing import with which it shares a longer
+ * prefix of dot-separated name segments.
+ */
+final class OrderPreservingImportAdder implements ImportAdder {
+ static class AdjacentImports {
+ final Collection<ImportName> importsBefore = new ArrayList<ImportName>();
+ final Collection<ImportName> importsAfter = new ArrayList<ImportName>();
+
+ @Override
+ public String toString() {
+ return String.format("(%s, %s)", this.importsBefore.toString(), this.importsAfter.toString()); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Returns the number of prefixing dot-separated segments shared between the two names.
+ * <p>
+ * For example, {@code countMatchingPrefixSegments("foo.pack1.Class", "foo.pack2.Class")} will
+ * return 1 and {@code countMatchingPrefixSegments("foo.pack1.Class", "com.foo.pack1.Class")}
+ * will return 0.
+ */
+ private static int countMatchingPrefixSegments(String name1, String name2) {
+ if (name1.isEmpty() || name2.isEmpty()) {
+ return 0;
+ }
+
+ int matchingSegments = 0;
+ for (int i = 0; i <= name1.length() && i <= name2.length(); i++) {
+ boolean atEndOfName1Segment = i == name1.length() || name1.charAt(i) == '.';
+ boolean atEndOfName2Segment = i == name2.length() || name2.charAt(i) == '.';
+ if (atEndOfName1Segment && atEndOfName2Segment) {
+ matchingSegments++;
+ } else if (name1.charAt(i) != name2.charAt(i)) {
+ break;
+ }
+ }
+
+ return matchingSegments;
+ }
+
+ private final Comparator<ImportName> importComparator;
+
+ OrderPreservingImportAdder(Comparator<ImportName> importComparator) {
+ this.importComparator = importComparator;
+ }
+
+ @Override
+ public List<ImportName> addImports(Collection<ImportName> existingImports, Collection<ImportName> importsToAdd) {
+ if (importsToAdd.isEmpty()) {
+ return new ArrayList<ImportName>(existingImports);
+ }
+
+ List<ImportName> sortedNewImports = new ArrayList<ImportName>(importsToAdd);
+ sortedNewImports.removeAll(new HashSet<ImportName>(existingImports));
+ Collections.sort(sortedNewImports, this.importComparator);
+
+ if (existingImports.isEmpty()) {
+ return sortedNewImports;
+ }
+
+ Map<ImportName, AdjacentImports> adjacentNewImports =
+ determineAdjacentNewImports(new ArrayList<ImportName>(existingImports), sortedNewImports);
+
+ List<ImportName> importsWithAdditions =
+ new ArrayList<ImportName>(existingImports.size() + sortedNewImports.size());
+ for (ImportName existingImport : existingImports) {
+ // Remove the adjacent imports so they don't get inserted multiple times in the case
+ // of duplicate imports.
+ AdjacentImports adjacentImports = adjacentNewImports.remove(existingImport);
+
+ if (adjacentImports != null) {
+ importsWithAdditions.addAll(adjacentImports.importsBefore);
+ }
+
+ importsWithAdditions.add(existingImport);
+
+ if (adjacentImports != null) {
+ importsWithAdditions.addAll(adjacentImports.importsAfter);
+ }
+ }
+
+ return importsWithAdditions;
+ }
+
+ /**
+ * Determines which new imports to place before and after each existing import.
+ * <p>
+ * Returns a Map where each key is an existing import and each corresponding value is an
+ * AdjacentImports containing those new imports which should be placed before and after that
+ * existing import. Each new import will be placed either before or after exactly one existing
+ * import.
+ *
+ * @param existingImports
+ * Existing imports.
+ * @param sortedNewImports
+ * Imports to be added. Must be in order as if sorted by this.importComparator.
+ */
+ private Map<ImportName, AdjacentImports> determineAdjacentNewImports(
+ Collection<ImportName> existingImports,
+ Iterable<ImportName> sortedNewImports) {
+ NavigableSet<ImportName> existingImportsTreeSet = new TreeSet<ImportName>(this.importComparator);
+ existingImportsTreeSet.addAll(existingImports);
+
+ Map<ImportName, AdjacentImports> adjacentNewImports = new HashMap<ImportName, AdjacentImports>();
+ for (ImportName existingImport : existingImports) {
+ adjacentNewImports.put(existingImport, new AdjacentImports());
+ }
+
+ for (ImportName newImport : sortedNewImports) {
+ ImportName precedingExistingImport = existingImportsTreeSet.lower(newImport);
+ ImportName succeedingExistingImport = existingImportsTreeSet.higher(newImport);
+
+ if (shouldGroupWithSucceeding(newImport, precedingExistingImport, succeedingExistingImport)) {
+ adjacentNewImports.get(succeedingExistingImport).importsBefore.add(newImport);
+ } else {
+ adjacentNewImports.get(precedingExistingImport).importsAfter.add(newImport);
+ }
+ }
+
+ return adjacentNewImports;
+ }
+
+ /**
+ * Returns true if the new import should be placed before the existing import that would succeed
+ * it in sorted order, or false if the new import should be placed after the existing import
+ * that would precede it in sorted order.
+ */
+ private boolean shouldGroupWithSucceeding(
+ ImportName newImport, ImportName precedingExistingImport, ImportName succeedingExistingImport) {
+ if (precedingExistingImport == null) {
+ return true;
+ } else if (succeedingExistingImport == null) {
+ return false;
+ } else {
+ String containerName = newImport.containerName;
+
+ int prefixSharedWithPreceding =
+ countMatchingPrefixSegments(containerName, precedingExistingImport.containerName);
+
+ int prefixSharedWithSucceeding =
+ countMatchingPrefixSegments(containerName, succeedingExistingImport.containerName);
+
+ return prefixSharedWithSucceeding > prefixSharedWithPreceding;
+ }
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OriginalImportEntry.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OriginalImportEntry.java
new file mode 100644
index 0000000000..482a264cd1
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OriginalImportEntry.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * Represents an import declaration that originally occurred in the compilation unit.
+ */
+class OriginalImportEntry extends ImportEntry {
+ /**
+ * The comments associated with (either preceding, embedded within, or following) this import
+ * declaration.
+ */
+ final List<ImportComment> comments;
+
+ /**
+ * The difference between the line number of the start of the import declaration (or the start
+ * of its first leading comment, if any) and the line number of the end of the preceding import
+ * declaration (or the end of that import's trailing comment, if any). Zero for the first import
+ * in the compilation unit.
+ */
+ final int precedingLineDelimiters;
+
+ /**
+ * The region of the compilation unit occupied by the whitespace (e.g. line delimiters) between
+ * the previous import (or its last trailing comment, if any) and this import declaration (or
+ * its first leading comment, if any).
+ */
+ final IRegion leadingDelimiter;
+
+ /**
+ * The region of the compilation unit occupied by the import declaration itself, its associated
+ * comments, and any whitespace between the import declaration and its comments.
+ */
+ final IRegion declarationAndComments;
+
+ OriginalImportEntry(
+ ImportName importName,
+ Collection<ImportComment> comments,
+ int precedingLeadingDelimiters,
+ IRegion leadingWhitespace,
+ IRegion declarationAndComments) {
+ super(importName);
+
+ this.comments = Collections.unmodifiableList(new ArrayList<ImportComment>(comments));
+ this.precedingLineDelimiters = precedingLeadingDelimiters;
+ this.leadingDelimiter = leadingWhitespace;
+ this.declarationAndComments = declarationAndComments;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("OriginalImportEntry(%s)", this.importName); //$NON-NLS-1$
+ }
+
+ @Override
+ boolean isOriginal() {
+ return true;
+ }
+
+ @Override
+ OriginalImportEntry asOriginalImportEntry() {
+ return this;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageAndContainingTypeImportComparator.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageAndContainingTypeImportComparator.java
new file mode 100644
index 0000000000..0f1b98d2e8
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageAndContainingTypeImportComparator.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.Comparator;
+
+/**
+ * Sorts imports according to a lexicographic comparison of their full container names (including
+ * package name and any containing type names).
+ * <p>
+ * The alternative is {@link PackageImportComparator}. See https://bugs.eclipse.org/194358.
+ */
+final class PackageAndContainingTypeImportComparator implements Comparator<ImportName> {
+ @Override
+ public int compare(ImportName o1, ImportName o2) {
+ return o1.containerName.compareTo(o2.containerName);
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageImportComparator.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageImportComparator.java
new file mode 100644
index 0000000000..275d74d2ca
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/PackageImportComparator.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.Comparator;
+
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.internal.core.JavaProject;
+
+/**
+ * Sorts imports according to a lexicographic comparison of their containing package names.
+ * <p>
+ * This requires use of the JavaProject to look up packages and/or types by name in order to
+ * distinguish segments of the import's container name as containing package name vs. containing
+ * type name.
+ * <p>
+ * The alternative is {@link PackageAndContainingTypeImportComparator}. See
+ * https://bugs.eclipse.org/194358.
+ */
+final class PackageImportComparator implements Comparator<ImportName> {
+ private final JavaProject javaProject;
+
+ PackageImportComparator(JavaProject javaProject) {
+ this.javaProject = javaProject;
+ }
+
+ @Override
+ public int compare(ImportName o1, ImportName o2) {
+ return determinePackageName(o1).compareTo(determinePackageName(o2));
+ }
+
+ private String determinePackageName(ImportName importName) {
+ String containerName = importName.containerName;
+
+ try {
+ // Loop from longest to shortest prefix (of dot-separated name segments) of the
+ // container name until a package name is found.
+ String containerNamePrefix = containerName;
+ while (true) {
+ // Try to find a package named with this prefix.
+ if (this.javaProject.findPackageFragment(containerNamePrefix) != null) {
+ return containerNamePrefix;
+ }
+
+ int lastSegmentStart = containerNamePrefix.lastIndexOf(Signature.C_DOT) + 1;
+
+ // Use the heuristic that a prefix whose last segment starts with a lowercase letter
+ // is a package name, if we don't recognize the prefix as the name of a type.
+ if (this.javaProject.findType(containerNamePrefix) == null) {
+ if (Character.isLowerCase(containerNamePrefix.charAt(lastSegmentStart))) {
+ return containerNamePrefix;
+ }
+ }
+
+ if (lastSegmentStart == 0) {
+ // No prefix of containerName could be resolved to a package name.
+ break;
+ }
+
+ containerNamePrefix = containerNamePrefix.substring(0, lastSegmentStart - 1);
+ }
+ } catch (JavaModelException e) {
+ // An error occurred when we asked the JavaProject to resolve a name,
+ // so there is no point in proceeding with the loop.
+ }
+
+ return containerName;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RemovedImportCommentReassigner.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RemovedImportCommentReassigner.java
new file mode 100644
index 0000000000..39eff125dd
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RemovedImportCommentReassigner.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Reassigns comments associated with removed imports (those present before but not present
+ * after the rewrite) to resultant imports (those present after the rewrite).
+ * <p>
+ * Reassigns comments of removed single imports to the first (in iteration order) resultant
+ * on-demand import having the same container name, if one exists.
+ * <p>
+ * Reassigns comments of removed on-demand imports to the first (in iteration order) resultant
+ * single import having the same container name, if one exists.
+ * <p>
+ * Leaves unassigned any removed import comment not matching the above cases.
+ */
+final class RemovedImportCommentReassigner {
+ private static Collection<OriginalImportEntry> retainImportsWithComments(Collection<OriginalImportEntry> imports) {
+ Collection<OriginalImportEntry> importsWithComments = new ArrayList<OriginalImportEntry>(imports.size());
+ for (OriginalImportEntry currentImport : imports) {
+ if (!currentImport.comments.isEmpty()) {
+ importsWithComments.add(currentImport);
+ }
+ }
+
+ return importsWithComments;
+ }
+
+ private static boolean hasFloatingComment(OriginalImportEntry nextAssignedImport) {
+ for (ImportComment importComment : nextAssignedImport.comments) {
+ if (importComment.succeedingLineDelimiters > 1) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private final Collection<OriginalImportEntry> originalImportsWithComments;
+
+ RemovedImportCommentReassigner(List<OriginalImportEntry> originalImports) {
+ this.originalImportsWithComments = retainImportsWithComments(originalImports);
+ }
+
+ /**
+ * Assigns comments of removed import entries (those in {@code originalImports} but not in
+ * {@code resultantImports}) to resultant import entries.
+ * <p>
+ * Returns a map containing the resulting assignments, where each key is an element of
+ * {@code resultantImports} and each value is a collection of comments reassigned to that
+ * resultant import.
+ */
+ Map<ImportEntry, Collection<ImportComment>> reassignComments(Collection<ImportEntry> resultantImports) {
+ Map<ImportEntry, Collection<OriginalImportEntry>> importAssignments = assignRemovedImports(resultantImports);
+
+ Map<ImportEntry, Collection<ImportComment>> commentAssignments =
+ new HashMap<ImportEntry, Collection<ImportComment>>();
+
+ for (Map.Entry<ImportEntry, Collection<OriginalImportEntry>> importAssignment : importAssignments.entrySet()) {
+ ImportEntry targetImport = importAssignment.getKey();
+ if (targetImport != null) {
+ Deque<ImportComment> assignedComments = new ArrayDeque<ImportComment>();
+
+ Collection<OriginalImportEntry> assignedImports = importAssignment.getValue();
+
+ Iterator<OriginalImportEntry> nextAssignedImportIterator = assignedImports.iterator();
+ if (nextAssignedImportIterator.hasNext()) {
+ nextAssignedImportIterator.next();
+ }
+
+ Iterator<OriginalImportEntry> assignedImportIterator = assignedImports.iterator();
+ while (assignedImportIterator.hasNext()) {
+ OriginalImportEntry currentAssignedImport = assignedImportIterator.next();
+ OriginalImportEntry nextAssignedImport =
+ nextAssignedImportIterator.hasNext() ? nextAssignedImportIterator.next() : null;
+
+ assignedComments.addAll(currentAssignedImport.comments);
+
+ if (nextAssignedImport != null && hasFloatingComment(nextAssignedImport)) {
+ // Ensure that a blank line separates this removed import's comments
+ // from the next removed import's floating comments.
+ ImportComment lastComment = assignedComments.removeLast();
+ ImportComment lastCommentWithTrailingBlankLine = new ImportComment(lastComment.region, 2);
+ assignedComments.add(lastCommentWithTrailingBlankLine);
+ }
+ }
+
+ commentAssignments.put(targetImport, assignedComments);
+ }
+ }
+
+ return commentAssignments;
+ }
+
+ private Map<ImportEntry, Collection<OriginalImportEntry>> assignRemovedImports(Collection<ImportEntry> imports) {
+ Collection<OriginalImportEntry> removedImportsWithComments = identifyRemovedImportsWithComments(imports);
+ if (removedImportsWithComments.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ Map<ImportName, ImportEntry> firstSingleForOnDemand = identifyFirstSingleForEachOnDemand(imports);
+ Map<ImportName, ImportEntry> firstOccurrences = identifyFirstOccurrenceOfEachImportName(imports);
+
+ Map<ImportEntry, Collection<OriginalImportEntry>> removedImportsForRetainedImport =
+ new HashMap<ImportEntry, Collection<OriginalImportEntry>>();
+ for (ImportEntry retainedImport : imports) {
+ removedImportsForRetainedImport.put(retainedImport, new ArrayList<OriginalImportEntry>());
+ }
+ // The null key will map to the removed imports not assigned to any import.
+ removedImportsForRetainedImport.put(null, new ArrayList<OriginalImportEntry>());
+
+ for (OriginalImportEntry removedImport : removedImportsWithComments) {
+ ImportName removedImportName = removedImport.importName;
+
+ final ImportEntry retainedImport;
+ if (removedImportName.isOnDemand()) {
+ retainedImport = firstSingleForOnDemand.get(removedImportName);
+ } else {
+ retainedImport = firstOccurrences.get(removedImportName.getContainerOnDemand());
+ }
+
+ // retainedImport will be null if there's no corresponding import to which to assign the removed import.
+ removedImportsForRetainedImport.get(retainedImport).add(removedImport);
+ }
+
+ return removedImportsForRetainedImport;
+ }
+
+ private Collection<OriginalImportEntry> identifyRemovedImportsWithComments(Collection<ImportEntry> imports) {
+ Collection<OriginalImportEntry> removedImports =
+ new ArrayList<OriginalImportEntry>(this.originalImportsWithComments);
+ removedImports.removeAll(imports);
+ return removedImports;
+ }
+
+ /**
+ * Assigns each removed on-demand import to the first single import in {@code imports} having
+ * the same container name.
+ * <p>
+ * Returns a map where each key is a single import and each value is the corresponding
+ * removed on-demand import.
+ * <p>
+ * The returned map only contains mappings to removed on-demand imports for which there are
+ * corresponding single imports in {@code imports}.
+ */
+ private Map<ImportName, ImportEntry> identifyFirstSingleForEachOnDemand(Iterable<ImportEntry> imports) {
+ Map<ImportName, ImportEntry> firstSingleImportForContainer = new HashMap<ImportName, ImportEntry>();
+ for (ImportEntry currentImport : imports) {
+ if (!currentImport.importName.isOnDemand()) {
+ ImportName containerOnDemand = currentImport.importName.getContainerOnDemand();
+ if (!firstSingleImportForContainer.containsKey(containerOnDemand)) {
+ firstSingleImportForContainer.put(containerOnDemand, currentImport);
+ }
+ }
+ }
+ return firstSingleImportForContainer;
+ }
+
+ private Map<ImportName, ImportEntry> identifyFirstOccurrenceOfEachImportName(Iterable<ImportEntry> imports) {
+ Map<ImportName, ImportEntry> firstOccurrenceOfImport = new HashMap<ImportName, ImportEntry>();
+ for (ImportEntry resultantImport : imports) {
+ if (!firstOccurrenceOfImport.containsKey(resultantImport.importName)) {
+ firstOccurrenceOfImport.put(resultantImport.importName, resultantImport);
+ }
+ }
+ return firstOccurrenceOfImport;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ReorderingImportAdder.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ReorderingImportAdder.java
new file mode 100644
index 0000000000..adc5e97aa7
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ReorderingImportAdder.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Totally sorts new and existing imports together, discarding the order of existing imports.
+ */
+final class ReorderingImportAdder implements ImportAdder {
+ private final Comparator<ImportName> importComparator;
+
+ ReorderingImportAdder(Comparator<ImportName> importComparator) {
+ this.importComparator = importComparator;
+ }
+
+ @Override
+ public List<ImportName> addImports(Collection<ImportName> existingImports, Collection<ImportName> importsToAdd) {
+ Set<ImportName> existingImportsSet = new HashSet<ImportName>(existingImports);
+
+ List<ImportName> importsWithAdditions = new ArrayList<ImportName>(existingImports.size() + importsToAdd.size());
+ importsWithAdditions.addAll(existingImports);
+ for (ImportName importToAdd : importsToAdd) {
+ if (!existingImportsSet.contains(importToAdd)) {
+ importsWithAdditions.add(importToAdd);
+ }
+ }
+
+ Collections.sort(importsWithAdditions, this.importComparator);
+
+ return importsWithAdditions;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RewriteSite.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RewriteSite.java
new file mode 100644
index 0000000000..715f152986
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/RewriteSite.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * Describes the location in the compilation unit to be occupied by import declarations.
+ */
+class RewriteSite {
+ /**
+ * The region where import declarations, their associated comments, and any adjacent whitespace
+ * should be placed.
+ * <p>
+ * Extends from the end of the AST node preceding import declarations and their comments (or the
+ * start of the compilation unit, if no such node exists) to the start of the AST node
+ * succeeding import declarations and their comments (or the end of the compilation unit, if no
+ * such node exists).
+ */
+ final IRegion surroundingRegion;
+
+ /**
+ * The region occupied by import declarations and their associated comments prior to the
+ * rewrite, or null if the compilation unit does not contain any import declarations.
+ * <p>
+ * If not null, this region is contained within surroundingRegion.
+ */
+ final IRegion importsRegion;
+
+ /**
+ * True if the compilation unit prior to the rewrite contains any top-level AST nodes (package
+ * declaration and/or comments) preceding the start of surroundingRegion.
+ */
+ final boolean hasPrecedingElements;
+
+ /**
+ * True if the compilation unit prior to the rewrite contains any top-level AST nodes (type
+ * declarations and/or comments) following the end of surroundingRegion.
+ */
+ final boolean hasSucceedingElements;
+
+ RewriteSite(
+ IRegion surroundingRegion,
+ IRegion importsRegion,
+ boolean hasPrecedingElements,
+ boolean hasSucceedingElements) {
+ this.surroundingRegion = surroundingRegion;
+ this.importsRegion = importsRegion;
+ this.hasPrecedingElements = hasPrecedingElements;
+ this.hasSucceedingElements = hasSucceedingElements;
+ }
+}
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/StaticConflictingSimpleNameFinder.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/StaticConflictingSimpleNameFinder.java
new file mode 100644
index 0000000000..d359d91939
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/StaticConflictingSimpleNameFinder.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Finds conflicts among importable static members declared within the specified on-demand-imported
+ * containers.
+ */
+final class StaticConflictingSimpleNameFinder implements ConflictingSimpleNameFinder {
+ private static boolean isStaticImportableMember(int memberFlags) {
+ return (Flags.isStatic(memberFlags) || Flags.isEnum(memberFlags)) && !Flags.isPrivate(memberFlags);
+ }
+
+ private final IJavaProject project;
+
+ StaticConflictingSimpleNameFinder(IJavaProject project) {
+ this.project = project;
+ }
+
+ @Override
+ public Set<String> findConflictingSimpleNames(
+ Set<String> simpleNames,
+ Set<String> onDemandAndImplicitContainerNames,
+ IProgressMonitor monitor) throws JavaModelException {
+ Set<String> memberNamesFoundInMultipleTypes = new HashSet<String>();
+
+ Set<String> foundMemberNames = new HashSet<String>();
+ for (String containerName : onDemandAndImplicitContainerNames) {
+ IType containingType = this.project.findType(containerName, monitor);
+ if (containingType != null) {
+ for (String memberName : extractStaticMemberNames(containingType)) {
+ if (simpleNames.contains(memberName)) {
+ if (foundMemberNames.contains(memberName)) {
+ memberNamesFoundInMultipleTypes.add(memberName);
+ } else {
+ foundMemberNames.add(memberName);
+ }
+ }
+ }
+ }
+ }
+
+ return memberNamesFoundInMultipleTypes;
+ }
+
+ private Set<String> extractStaticMemberNames(IType type) throws JavaModelException {
+ Set<String> memberNames = new HashSet<String>();
+
+ for (IField field : type.getFields()) {
+ if (isStaticImportableMember(field.getFlags())) {
+ memberNames.add(field.getElementName());
+ }
+ }
+
+ for (IMethod method : type.getMethods()) {
+ if (isStaticImportableMember(method.getFlags())) {
+ memberNames.add(method.getElementName());
+ }
+ }
+
+ return memberNames;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/TypeConflictingSimpleNameFinder.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/TypeConflictingSimpleNameFinder.java
new file mode 100644
index 0000000000..2e96c96a8c
--- /dev/null
+++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/TypeConflictingSimpleNameFinder.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Google Inc 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:
+ * John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.dom.rewrite.imports;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.IJavaSearchConstants;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.TypeNameRequestor;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+final class TypeConflictingSimpleNameFinder implements ConflictingSimpleNameFinder {
+ private static class ConflictAccumulatingTypeRequestor extends TypeNameRequestor {
+ private static String buildContainerName(char[] packageName, char[][] enclosingTypeNames) {
+ StringBuffer buf= new StringBuffer();
+ buf.append(packageName);
+ for (char[] enclosingTypeName : enclosingTypeNames) {
+ if (buf.length() > 0)
+ buf.append('.');
+ buf.append(enclosingTypeName);
+ }
+ return buf.toString();
+ }
+
+ private final Set<String> namesFoundInMultipleContainers;
+ private final Map<String, String> lastContainerFoundForType;
+
+ ConflictAccumulatingTypeRequestor() {
+ this.namesFoundInMultipleContainers = new HashSet<String>();
+ this.lastContainerFoundForType = new HashMap<String, String>();
+ }
+
+ @Override
+ public void acceptType(
+ int modifiers,
+ char[] packageName,
+ char[] simpleTypeName,
+ char[][] enclosingTypeNames,
+ String path) {
+ String simpleName = new String(simpleTypeName);
+ String newContainerName = buildContainerName(packageName, enclosingTypeNames);
+ String oldContainerName = this.lastContainerFoundForType.put(simpleName, newContainerName);
+ if (oldContainerName != null && !oldContainerName.equals(newContainerName)) {
+ this.namesFoundInMultipleContainers.add(simpleName);
+ }
+ }
+
+ Set<String> getNamesFoundInMultipleContainers() {
+ return Collections.unmodifiableSet(this.namesFoundInMultipleContainers);
+ }
+ }
+
+ private static char[][] stringsToCharArrays(Collection<String> strings) {
+ char[][] arrayOfArrays = new char[strings.size()][];
+ int i = 0;
+ for (String string : strings) {
+ arrayOfArrays[i] = string.toCharArray();
+ i++;
+ }
+ return arrayOfArrays;
+ }
+
+ private final IJavaProject javaProject;
+ private final SearchEngine searchEngine;
+
+ TypeConflictingSimpleNameFinder(IJavaProject javaProject, SearchEngine searchEngine) {
+ this.javaProject = javaProject;
+ this.searchEngine = searchEngine;
+ }
+
+ @Override
+ public Set<String> findConflictingSimpleNames(
+ Set<String> simpleNames,
+ Set<String> onDemandAndImplicitContainerNames,
+ IProgressMonitor monitor) throws JavaModelException {
+ IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { this.javaProject });
+
+ ConflictAccumulatingTypeRequestor requestor = new ConflictAccumulatingTypeRequestor();
+
+ this.searchEngine.searchAllTypeNames(
+ stringsToCharArrays(onDemandAndImplicitContainerNames),
+ stringsToCharArrays(simpleNames),
+ scope,
+ requestor,
+ IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH,
+ monitor);
+
+ return requestor.getNamesFoundInMultipleContainers();
+ }
+} \ No newline at end of file

Back to the top