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

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

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2000, 2023 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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.aspectj.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.aspectj.org.eclipse.jdt.core.IBuffer;
import org.aspectj.org.eclipse.jdt.core.ICompilationUnit;
import org.aspectj.org.eclipse.jdt.core.IJavaProject;
import org.aspectj.org.eclipse.jdt.core.JavaCore;
import org.aspectj.org.eclipse.jdt.core.JavaModelException;
import org.aspectj.org.eclipse.jdt.core.dom.ASTNode;
import org.aspectj.org.eclipse.jdt.core.dom.Comment;
import org.aspectj.org.eclipse.jdt.core.dom.CompilationUnit;
import org.aspectj.org.eclipse.jdt.core.dom.ImportDeclaration;
import org.aspectj.org.eclipse.jdt.core.dom.PackageDeclaration;
import org.aspectj.org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.aspectj.org.eclipse.jdt.core.search.SearchEngine;
import org.aspectj.org.eclipse.jdt.internal.core.JavaProject;
import org.aspectj.org.eclipse.jdt.internal.core.dom.rewrite.imports.ConflictIdentifier.Conflicts;
import org.aspectj.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; if (!nodesTreeMap.isEmpty()) { ASTNode topNode = nodesTreeMap.firstEntry().getValue(); if (topNode instanceof Comment) { for (ASTNode node : nodesTreeMap.values()) { if (!(node instanceof Comment)) { break; } else { surroundingStart = node.getStartPosition() + node.getLength(); } } } } } 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