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

org.aspectj.org.eclipse.jdt.internal.core.dom.rewrite.imports.ImportEditor 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) 2015 Google Inc 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:
 *     John Glassmyer  - import group sorting is broken - https://bugs.eclipse.org/430303
 *     Lars Vogel  - Contributions for
 *     						Bug 473178
 *******************************************************************************/
package org.aspectj.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 originalImportIterator;
		OriginalImportEntry currentOriginalImport;
		int currentPosition;

		OriginalImportsCursor(int startPosition, Collection 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 leadingDelimiterEdits;
		final Collection commentAndDeclarationEdits;

		ImportEdits(
				Collection leadingDelimiterEdits,
				Collection 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.
	 * 

* Maps by identity (rather than by hashcode) to handle cases of duplicate import declarations. */ private static Map mapPrecedingImports(Collection importEntries) { Map precedingImports = new IdentityHashMap<>(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 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 originalPrecedingImports; private final List 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 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. *

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

* Relocates leading and trailing comments of removed imports as determined by * {@link #commentReassigner}. */ TextEdit createTextEdit(Collection 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 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 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 determineEditsForImports( IRegion importsRegion, Collection resultantImports) { Collection edits = new ArrayList<>(); Map> 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 editsWithoutRangeMarkers = new ArrayList<>(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 placeResultantImports( OriginalImportsCursor cursor, Collection resultantImports, Map> commentReassignments) { Collection edits = new ArrayList<>(); 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 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.emptySet(), Collections.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 leadingDelimiterEdits = Arrays.asList(leadingSourceEdit, leadingTargetEdit); MoveSourceEdit importSourceEdit = new MoveSourceEdit( importEntry.declarationAndComments.getOffset(), importEntry.declarationAndComments.getLength()); MoveTargetEdit importTargetEdit = new MoveTargetEdit(position, importSourceEdit); Collection 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.singleton(new RangeMarker( importEntry.leadingDelimiter.getOffset(), importEntry.leadingDelimiter.getLength())), Collections.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). *

* 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 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 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 relocateComments(Collection reassignedComments, int insertPosition) { if (reassignedComments.isEmpty()) { return Collections.emptyList(); } Collection edits = new ArrayList<>(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 deleteRemainingText(IRegion importRegion, Collection edits) { List sortedEdits = new ArrayList<>(edits); Collections.sort(sortedEdits, new Comparator() { @Override public int compare(TextEdit o1, TextEdit o2) { return o1.getOffset() - o2.getOffset(); } }); int deletePosition = importRegion.getOffset(); Collection deleteRemainingTextEdits = new ArrayList<>(); 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy