All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jdt.internal.core.dom.rewrite.imports.ImportRewriteAnalyzer Maven / Gradle / Ivy

/*******************************************************************************
 * 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  - 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.IBuffer;
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.
 * 

* Operates in either of two modes (selected via {@link ImportRewriteConfiguration.Builder}'s * static factory methods): *

    *
  • Discarding original imports and totally sorting all imports added thereafter. This mode is * used by the Organize Imports operation.
  • *
  • 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.
  • *
*/ 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 createdImports; RewriteResult(TextEdit textEdit, Set 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 imports) { List names = new ArrayList(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 readOriginalImports(CompilationUnit compilationUnit) { List importDeclarations = compilationUnit.imports(); if (importDeclarations.isEmpty()) { return Collections.emptyList(); } List 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 imports = new ArrayList(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 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 selectImportComments( CompilationUnit compilationUnit, List comments, int importDeclarationStartPosition, int commentStartIndex, int commentEndIndex) { List importComments = new ArrayList(comments.size()); Iterator 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 originalImports) throws JavaModelException { 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. *

* Returns null if originalImports is null or empty. */ private static IRegion determineImportsRegion(List 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) throws JavaModelException { NavigableMap 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 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(); } int surroundingEnd = positionAfterImports; IBuffer buffer = compilationUnit.getTypeRoot().getBuffer(); int length = buffer.getLength(); while (surroundingEnd < length && Character.isWhitespace(buffer.getChar(surroundingEnd))) { surroundingEnd++; } 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 mapTopLevelNodes(CompilationUnit compilationUnit) { NavigableMap map = new TreeMap(); Collection nodes = new ArrayList(); if (compilationUnit.getPackage() != null) { nodes.add(compilationUnit.getPackage()); } nodes.addAll(compilationUnit.imports()); nodes.addAll(compilationUnit.types()); for (Comment comment : ((List) 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 mapImportsByNameIdentity(List imports) { Map importsByName = new IdentityHashMap(); 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 subtractImports( Collection existingImports, Set importsToSubtract) { List remainingImports = new ArrayList(existingImports.size()); for (ImportName existingImport : existingImports) { if (!importsToSubtract.contains(existingImport)) { remainingImports.add(existingImport); } } return remainingImports; } private final List originalImportEntries; private final List originalImportsList; private final Set originalImportsSet; private final ImportDeclarationWriter importDeclarationWriter; private final ImportAdder importAdder; private final Set importsToAdd; private final Set importsToRemove; private final boolean reportAllResultantImportsAsCreated; private final Set typeExplicitSimpleNames; private final Set staticExplicitSimpleNames; private final Set implicitImportContainerNames; private final ConflictIdentifier conflictIdentifier; private final OnDemandComputer onDemandComputer; private final Map 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 importsList = new ArrayList(this.originalImportEntries.size()); Set importsSet = new HashSet(); 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(); this.importsToRemove = new LinkedHashSet(); if (configuration.originalImportHandling.shouldRemoveOriginalImports()) { this.importsToRemove.addAll(importsSet); this.reportAllResultantImportsAsCreated = true; } else { this.reportAllResultantImportsAsCreated = false; } this.typeExplicitSimpleNames = new HashSet(); this.staticExplicitSimpleNames = new HashSet(); 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. *

* Has no effect if the compilation unit otherwise would contain the given import. *

* 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. *

* Has no effect if the compilation unit otherwise would not contain the given import. *

* 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}. *

* This method has no side-effects. */ public RewriteResult analyzeRewrite(IProgressMonitor monitor) throws JavaModelException { List computedImportOrder = computeImportOrder(monitor); List resultingImportEntries = matchExistingOrCreateNew(computedImportOrder); TextEdit edit = this.importEditor.createTextEdit(resultingImportEntries); Set createdImports = new HashSet(computedImportOrder); if (!this.reportAllResultantImportsAsCreated) { createdImports.removeAll(this.originalImportsSet); } return new RewriteResult(edit, createdImports); } private List computeImportOrder(IProgressMonitor progressMonitor) throws JavaModelException { Set importsWithAdditionsAndRemovals = new HashSet(this.originalImportsSet); importsWithAdditionsAndRemovals.addAll(this.importsToAdd); importsWithAdditionsAndRemovals.removeAll(this.importsToRemove); Set touchedContainers = determineTouchedContainers(); Conflicts conflicts = this.conflictIdentifier.identifyConflicts( importsWithAdditionsAndRemovals, touchedContainers, this.typeExplicitSimpleNames, this.staticExplicitSimpleNames, progressMonitor); Set allTypeExplicitSimpleNames = new HashSet(this.typeExplicitSimpleNames); allTypeExplicitSimpleNames.addAll(conflicts.typeConflicts); Set allStaticExplicitSimpleNames = new HashSet(this.staticExplicitSimpleNames); allStaticExplicitSimpleNames.addAll(conflicts.staticConflicts); Set implicitImports = identifyImplicitImports(this.importsToAdd, allTypeExplicitSimpleNames); List importsWithoutImplicits = subtractImports(importsWithAdditionsAndRemovals, implicitImports); Collection onDemandReductions = this.onDemandComputer.identifyPossibleReductions( new HashSet(importsWithoutImplicits), touchedContainers, allTypeExplicitSimpleNames, allStaticExplicitSimpleNames); ImportsDelta delta = computeDelta(implicitImports, onDemandReductions); List importsWithRemovals = subtractImports(this.originalImportsList, delta.importsToRemove); List importsWithAdditions = this.importAdder.addImports(importsWithRemovals, delta.importsToAdd); return importsWithAdditions; } private Set determineTouchedContainers() { Collection touchedContainers = new ArrayList( 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(touchedContainers)); } private Set identifyImplicitImports( Collection addedImports, Set allTypeExplicitSimpleNames) { if (this.implicitImportContainerNames.isEmpty()) { return Collections.emptySet(); } Collection implicits = new ArrayList(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(implicits); } private List matchExistingOrCreateNew(Collection importNames) { List importEntries = new ArrayList(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 implicitImports, Collection onDemandReductions) { Collection additions = new ArrayList(this.originalImportsList.size()); additions.addAll(this.importsToAdd); Collection removals = new ArrayList(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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy