/******************************************************************************* * Copyright (c) 2000, 2011 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 *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.internal.core.dom.rewrite; import org.eclipse.che.ide.ext.java.jdt.core.JavaCore; import org.eclipse.che.ide.ext.java.jdt.core.Signature; import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation; import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode; import org.eclipse.che.ide.ext.java.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.Comment; import org.eclipse.che.ide.ext.java.jdt.core.dom.CompilationUnit; import org.eclipse.che.ide.ext.java.jdt.core.dom.ImportDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.LineComment; import org.eclipse.che.ide.ext.java.jdt.core.dom.PackageDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.che.ide.ext.java.jdt.text.Document; import org.eclipse.che.ide.ext.java.jdt.text.edits.DeleteEdit; import org.eclipse.che.ide.ext.java.jdt.text.edits.InsertEdit; import org.eclipse.che.ide.ext.java.jdt.text.edits.MultiTextEdit; import org.eclipse.che.ide.api.text.BadLocationException; import org.eclipse.che.ide.api.text.Region; import org.eclipse.che.ide.api.text.RegionImpl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; public final class ImportRewriteAnalyzer { private final Document document; private final ArrayList<PackageEntry> packageEntries; private final List<String> importsCreated; private final List<String> staticImportsCreated; private final Region replaceRange; private final int importOnDemandThreshold; private final int staticImportOnDemandThreshold; private boolean filterImplicitImports; private boolean useContextToFilterImplicitImports; private boolean findAmbiguousImports; private Region[] 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(Document document, 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<ImportRewriteAnalyzer.PackageEntry>(20); this.importsCreated = new ArrayList<String>(); this.staticImportsCreated = new ArrayList<String>(); this.flags = 0; this.document = document; 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(JavaCore .getOption(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS)); if (num >= 0) return num; } catch (NumberFormatException e) { // fall through } return 1; } private boolean insertSpaceBeforeSemicolon() { return JavaCore.INSERT.equals(JavaCore .getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_SEMICOLON)); } 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 = 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 = 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 = decls.get(i); int nextOffset = next.getStartPosition(); int nextLength = next.getLength(); int nextOffsetLine = root.getLineNumber(nextOffset); int extendedStart = root.getExtendedStartPosition(next); int extendedLength = root.getExtendedLength(next); // if next import is on a different line, modify the end position to the next line begin offset if (currEndLine < nextOffsetLine) { currEndLine++; nextOffset = root.getPosition(currEndLine, 0); } // retrieve preceding and trailing comments if any Region rangeBefore = null; Region rangeAfter = null; if (nextOffset != extendedStart) { rangeBefore = new RegionImpl(extendedStart, extendedStart - nextOffset + 1); } if (nextLength != extendedLength) { rangeAfter = new RegionImpl(nextOffset + nextLength, extendedLength - nextLength + 1); } currPackage.add(new ImportDeclEntry(packName.length(), name, isStatic, new RegionImpl(currOffset, nextOffset - currOffset), rangeBefore, rangeAfter)); currOffset = nextOffset; curr = next; // add a comment entry for spacing between imports if (currEndLine < nextOffsetLine) { nextOffset = root.getPosition(nextOffsetLine, 0); currPackage = new PackageEntry(); // create a comment package entry for this this.packageEntries.add(currPackage); currPackage.add(new ImportDeclEntry(packName.length(), null, false, new RegionImpl(currOffset, nextOffset - currOffset))); 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 length = this.replaceRange.getOffset() + this.replaceRange.getLength() - curr.getStartPosition(); currPackage.add(new ImportDeclEntry(packName.length(), name, isStatic, new RegionImpl(curr.getStartPosition(), length))); } private Region[] retrieveExistingCommentsInImports(CompilationUnit root) { List<ImportDeclaration> decls = root.imports(); if (decls.isEmpty()) { return null; } List<Comment> commentList = root.getCommentList(); int numberOfComments = commentList.size(); List<Region> 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 = 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<Region>(); } regions.add(new RegionImpl(extendedStart, lengthOfPrecedingComment)); } if (extendedLength != (nextLength + lengthOfPrecedingComment)) { // Preceding and trailing comments int regionLength = extendedLength - (nextLength + lengthOfPrecedingComment); if (regions == null) { regions = new ArrayList<Region>(); } regions.add(new RegionImpl(nextOffset + nextLength, regionLength)); } } else if (nextLength != extendedLength) { // no extended start - only trailing comment int regionLength = extendedLength - nextLength; if (regions == null) { regions = new ArrayList<Region>(); } regions.add(new RegionImpl(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<Comment>() { public int compare(Comment o1, Comment o2) { return o1.getStartPosition() - 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<Region>(); } regions.add(new RegionImpl(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 Region[] result = (Region[])regions.toArray(new RegionImpl[regions.size()]); Arrays.sort(result, new Comparator<Region>() { public int compare(Region o1, Region o2) { return o1.getOffset() - 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; // 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()) { return curr; // perfect fit, use entry } if ((newName.charAt(prefixLen) == '.' || prefixLen == 0) && prefixLen > longestPrefix) { longestPrefix = prefixLen; groupId = currGroup; } } } } 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(Signature.getQualifier(qualifier))) { return true; } // TODO // 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); return false; } public void addImport(String fullTypeName, boolean isStatic) { String typeContainerName = getQualifier(fullTypeName, isStatic); ImportDeclEntry decl = new ImportDeclEntry(typeContainerName.length(), fullTypeName, isStatic, null); sortIn(typeContainerName, decl, isStatic); } 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 Region evaluateReplaceRange(CompilationUnit root) { List<ImportDeclaration> imports = root.imports(); if (!imports.isEmpty()) { ImportDeclaration first = imports.get(0); ImportDeclaration last = 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 RegionImpl(startPos, endPos - startPos); } else { int start = getPackageStatementEndPos(root); return new RegionImpl(start, 0); } } public MultiTextEdit getResultingEdits() throws BadLocationException { int importsStart = this.replaceRange.getOffset(); int importsLen = this.replaceRange.getLength(); String lineDelim = "\n"; // 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<String> onDemandConflicts = null; if (this.findAmbiguousImports) { onDemandConflicts = evaluateStarImportConflicts(); } int spacesBetweenGroups = getSpacesBetweenImportGroups(); ArrayList<String> stringsToInsert = new ArrayList<String>(); 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) { // add a space between two different groups by looking at the two adjacent imports if (lastPackage != null && !pack.isComment() && !pack.isSameGroup(lastPackage)) { ImportDeclEntry last = lastPackage.getImportAt(lastPackage.getNumberOfImports() - 1); ImportDeclEntry first = pack.getImportAt(0); if (!lastPackage.isComment() && (last.isNew() || first.isNew())) { for (int k = spacesBetweenGroups; k > 0; k--) { stringsToInsert.add(lineDelim); } } } } lastPackage = pack; boolean isStatic = pack.isStatic(); int threshold = isStatic ? this.staticImportOnDemandThreshold : this.importOnDemandThreshold; boolean doStarImport = pack.hasStarImport(threshold, onDemandConflicts); if (doStarImport && (pack.find("*") == null)) { //$NON-NLS-1$ String[] imports = getNewImportStrings(document, pack, isStatic, lineDelim); for (int j = 0, max = imports.length; j < max; j++) { stringsToInsert.add(imports[j]); } } for (int k = 0; k < nImports; k++) { ImportDeclEntry currDecl = pack.getImportAt(k); Region region = currDecl.getSourceRange(); if (region == null) { // new entry if (!doStarImport || currDecl.isOnDemand() || (onDemandConflicts != null && onDemandConflicts.contains(currDecl.getSimpleName()))) { String str = getNewImportString(currDecl.getElementName(), isStatic, lineDelim); stringsToInsert.add(str); } else if (doStarImport && !currDecl.isOnDemand()) { 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 (!doStarImport || currDecl.isOnDemand() || onDemandConflicts == null || onDemandConflicts.contains(currDecl.getSimpleName())) { int offset = region.getOffset(); removeAndInsertNew(document, currPos, offset, stringsToInsert, resEdit); stringsToInsert.clear(); currPos = offset + region.getLength(); } else if (doStarImport && !currDecl.isOnDemand()) { String simpleName = currDecl.getTypeQualifiedName(); if (simpleName.indexOf('.') != -1) { Region rangeBefore = currDecl.getPrecedingCommentRange(); if (rangeBefore != null) { stringsToInsert.add(document.get(rangeBefore.getOffset(), rangeBefore.getLength())); } Region rangeAfter = currDecl.getTrailingCommentRange(); String trailingComment = null; if (rangeAfter != null) { trailingComment = document.get(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; i++) { Region region = this.preserveExistingCommentsRanges[i]; String text = document.get(region.getOffset(), region.getLength()); // remove preceding whitespaces int index = 0; int length = text.length(); loop: while (index < length) { if (CharOperation.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(document, 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; } private void removeAndInsertNew(Document document, int contentOffset, int contentEnd, ArrayList<String> stringsToInsert, MultiTextEdit resEdit) throws BadLocationException { int pos = contentOffset; for (int i = 0; i < stringsToInsert.size(); i++) { String curr = stringsToInsert.get(i); int idx = findInBuffer(document, 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(Document document, String str, int start, int end) throws BadLocationException { 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 (document.getChar(pos) == first) { int k = 1; while (k < len && document.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<String> evaluateStarImportConflicts() { // long start= System.currentTimeMillis(); final HashSet<String> onDemandConflicts = new HashSet<String>(); // IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaElement[] { this.compilationUnit.getJavaProject() }); ArrayList<char[]> starImportPackages = new ArrayList<char[]>(); ArrayList<char[]> simpleTypeNames = new ArrayList<char[]>(); 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 = starImportPackages.toArray(new char[starImportPackages.size()][]); // char[][] allTypes = 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(Document document, PackageEntry packageEntry, boolean isStatic, String lineDelim) throws BadLocationException { boolean isStarImportAdded = false; List<String> allImports = new ArrayList<String>(); int nImports = packageEntry.getNumberOfImports(); StringBuffer allComments = null; for (int i = 0; i < nImports; i++) { ImportDeclEntry curr = packageEntry.getImportAt(i); String simpleName = curr.getTypeQualifiedName(); if (simpleName.indexOf('.') != -1) { // member type imports - we preserve it Region rangeBefore = curr.getPrecedingCommentRange(); if (rangeBefore != null) { allImports.add(document.get(rangeBefore.getOffset(), rangeBefore.getLength())); } Region rangeAfter = curr.getTrailingCommentRange(); String trailingComment = null; if (rangeAfter != null) { trailingComment = document.get(rangeAfter.getOffset(), rangeAfter.getLength()); } allImports.add(getNewImportString(curr.getElementName(), isStatic, trailingComment, lineDelim)); } else if (!isStarImportAdded) { String starImportString = packageEntry.getName() + ".*"; //$NON-NLS-1$ allImports.add(getNewImportString(starImportString, isStatic, lineDelim)); isStarImportAdded = true; } else { // collect all comments Region rangeBefore = curr.getPrecedingCommentRange(); if (rangeBefore != null) { if (allComments == null) { allComments = new StringBuffer(); } allComments.append(document.get(rangeBefore.getOffset(), rangeBefore.getLength())).append(lineDelim); } Region rangeAfter = curr.getTrailingCommentRange(); if (rangeAfter != null) { if (allComments == null) { allComments = new StringBuffer(); } allComments.append(document.get(rangeAfter.getOffset(), rangeAfter.getLength())).append(lineDelim); } } } if (allComments != null) { allImports.add(0, String.valueOf(allComments)); } return allImports.toArray(new String[allImports.size()]); } private static int getFirstTypeBeginPos(CompilationUnit root) { List<AbstractTypeDeclaration> 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 Region sourceRange; private final boolean isStatic; private int containerNameLength; private Region precedingCommentRange; private Region trailingCommentRange; public ImportDeclEntry(int containerNameLength, String elementName, boolean isStatic, Region sourceRange, Region precedingCommentRange, Region trailingCommentRange) { this(containerNameLength, elementName, isStatic, sourceRange); this.precedingCommentRange = precedingCommentRange; this.trailingCommentRange = trailingCommentRange; } public ImportDeclEntry(int containerNameLength, String elementName, boolean isStatic, Region 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 Region getSourceRange() { return this.sourceRange; } public Region getPrecedingCommentRange() { return this.precedingCommentRange; } public Region 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<ImportDeclEntry> 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<ImportRewriteAnalyzer.ImportDeclEntry>(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<String> 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.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()]); } }