blob: 463eb54f5c3375f908a80d2c83642c3b11f12d35 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2013, 2014, 2015 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 <jogl@google.com> - 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.
* <p>
* Operates in either of two modes (selected via {@link ImportRewriteConfiguration.Builder}'s
* static factory methods):
* <ul>
* <li>Discarding original imports and totally sorting all imports added thereafter. This mode is
* used by the Organize Imports operation.</li>
* <li>Preserving original imports and placing each added import adjacent to the one most closely
* matching it. This mode is used e.g. when Content Assist adds an import for a completed name.</li>
* </ul>
*/
public final class ImportRewriteAnalyzer {
/**
* Encapsulates, for a computed import rewrite, a {@code TextEdit} that can be applied to effect
* the rewrite as well as the names of imports created by the rewrite.
*/
public static final class RewriteResult {
private final TextEdit textEdit;
private final Set<ImportName> createdImports;
RewriteResult(TextEdit textEdit, Set<ImportName> createdImports) {
this.textEdit = textEdit;
this.createdImports = Collections.unmodifiableSet(createdImports);
}
/**
* Returns a {@link TextEdit} describing the changes necessary to perform the rewrite.
*/
public TextEdit getTextEdit() {
return this.textEdit;
}
public String[] getCreatedImports() {
return extractQualifiedNames(false, this.createdImports);
}
public String[] getCreatedStaticImports() {
return extractQualifiedNames(true, this.createdImports);
}
private String[] extractQualifiedNames(boolean b, Collection<ImportName> imports) {
List<String> names = new ArrayList<String>(imports.size());
for (ImportName importName : imports) {
if (importName.isStatic == b) {
names.add(importName.qualifiedName);
}
}
return names.toArray(new String[names.size()]);
}
}
/**
* Returns the value of the formatter option specifying how many blank lines to insert between
* import groups.
*/
private static int getBlankLinesBetweenImportGroups(IJavaProject javaProject) {
int num = -1;
String blankLinesOptionValue =
javaProject.getOption(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS, true);
try {
num = Integer.parseInt(blankLinesOptionValue);
} catch (NumberFormatException e) {
String message = String.format(
"Could not parse the value of %s as an integer: %s", //$NON-NLS-1$
DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS,
blankLinesOptionValue);
Util.log(new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, message, e));
}
return num >= 0 ? num : 1;
}
/**
* Returns the value of the formatter option specifying whether to insert a space between the
* imported name and the semicolon in an import declaration.
*/
private static boolean shouldInsertSpaceBeforeSemicolon(IJavaProject javaProject) {
return JavaCore.INSERT.equals(
javaProject.getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_SEMICOLON, true));
}
/**
* Reads the positions of each existing import declaration along with any associated comments,
* and returns these in a list whose iteration order reflects the existing order of the imports
* in the compilation unit.
*/
private static List<OriginalImportEntry> readOriginalImports(CompilationUnit compilationUnit) {
List<ImportDeclaration> importDeclarations = compilationUnit.imports();
if (importDeclarations.isEmpty()) {
return Collections.emptyList();
}
List<Comment> comments = compilationUnit.getCommentList();
int currentCommentIndex = 0;
// Skip over package and file header comments (see https://bugs.eclipse.org/121428).
ImportDeclaration firstImport = importDeclarations.get(0);
PackageDeclaration packageDeclaration = compilationUnit.getPackage();
int firstImportStartPosition = packageDeclaration == null
? firstImport.getStartPosition()
: compilationUnit.getExtendedStartPosition(packageDeclaration)
+ compilationUnit.getExtendedLength(packageDeclaration);
while (currentCommentIndex < comments.size()
&& comments.get(currentCommentIndex).getStartPosition() < firstImportStartPosition) {
currentCommentIndex++;
}
List<OriginalImportEntry> imports = new ArrayList<OriginalImportEntry>(importDeclarations.size());
int previousExtendedEndPosition = -1;
for (ImportDeclaration currentImport : importDeclarations) {
int extendedEndPosition = compilationUnit.getExtendedStartPosition(currentImport)
+ compilationUnit.getExtendedLength(currentImport);
int commentAfterImportIndex = currentCommentIndex;
while (commentAfterImportIndex < comments.size()
&& comments.get(commentAfterImportIndex).getStartPosition() < extendedEndPosition) {
commentAfterImportIndex++;
}
List<ImportComment> importComments;
if (commentAfterImportIndex == currentCommentIndex) {
importComments = Collections.emptyList();
} else {
importComments = selectImportComments(
compilationUnit,
comments,
currentImport.getStartPosition(),
currentCommentIndex,
commentAfterImportIndex);
}
int importAndCommentsStartPosition = importComments.isEmpty()
? currentImport.getStartPosition()
: Math.min(currentImport.getStartPosition(), importComments.get(0).region.getOffset());
IRegion leadingWhitespaceRegion;
int precedingLineDelimiters;
if (previousExtendedEndPosition == -1) {
leadingWhitespaceRegion = new Region(importAndCommentsStartPosition, 0);
precedingLineDelimiters = 0;
} else {
leadingWhitespaceRegion = new Region(
previousExtendedEndPosition, importAndCommentsStartPosition - previousExtendedEndPosition);
int importAndCommentsFirstLine = compilationUnit.getLineNumber(importAndCommentsStartPosition);
int lastLineOfPrevious = compilationUnit.getLineNumber(previousExtendedEndPosition - 1);
precedingLineDelimiters = importAndCommentsFirstLine - lastLineOfPrevious;
}
IRegion importAndCommentsRegion =
new Region(importAndCommentsStartPosition, extendedEndPosition - importAndCommentsStartPosition);
imports.add(new OriginalImportEntry(
ImportName.createFor(currentImport),
importComments,
precedingLineDelimiters,
leadingWhitespaceRegion,
importAndCommentsRegion));
currentCommentIndex = commentAfterImportIndex;
previousExtendedEndPosition = extendedEndPosition;
}
return imports;
}
private static List<ImportComment> selectImportComments(
CompilationUnit compilationUnit,
List<Comment> comments,
int importDeclarationStartPosition,
int commentStartIndex,
int commentEndIndex) {
List<ImportComment> importComments = new ArrayList<ImportComment>(comments.size());
Iterator<Comment> commentIterator = comments.subList(commentStartIndex, commentEndIndex).iterator();
Comment currentComment = commentIterator.hasNext() ? commentIterator.next() : null;
while (currentComment != null) {
int currentCommentStartPosition = currentComment.getStartPosition();
int currentCommentLength = currentComment.getLength();
Comment nextComment = commentIterator.hasNext() ? commentIterator.next() : null;
int succeedingLineDelims;
int nextCommentStartPosition = nextComment == null ? Integer.MAX_VALUE : nextComment.getStartPosition();
int nextStartPosition = Math.min(importDeclarationStartPosition, nextCommentStartPosition);
if (nextStartPosition == Integer.MAX_VALUE) {
// This trailing comment is located at the end of the import's extended range
// and we don't care how many line delimiters follow it.
succeedingLineDelims = 0;
} else {
int currentCommentEndLine =
compilationUnit.getLineNumber(currentCommentStartPosition + currentCommentLength);
int nextStartLine = compilationUnit.getLineNumber(nextStartPosition);
succeedingLineDelims = nextStartLine - currentCommentEndLine;
}
importComments.add(new ImportComment(
new Region(currentCommentStartPosition, currentCommentLength), succeedingLineDelims));
currentComment = nextComment;
}
return importComments;
}
private static RewriteSite determineRewriteSite(
CompilationUnit compilationUnit, List<OriginalImportEntry> originalImports) 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.
* <p>
* Returns null if originalImports is null or empty.
*/
private static IRegion determineImportsRegion(List<OriginalImportEntry> originalImports) {
if (originalImports == null || originalImports.isEmpty()) {
return null;
}
OriginalImportEntry firstImport = originalImports.get(0);
int start = firstImport.declarationAndComments.getOffset();
OriginalImportEntry lastImport = originalImports.get(originalImports.size() - 1);
int end = lastImport.declarationAndComments.getOffset()
+ lastImport.declarationAndComments.getLength();
return new Region(start, end - start);
}
/**
* Determines the region to be occupied by imports, their associated comments, and surrounding
* whitespace.
*/
private static IRegion determineSurroundingRegion(CompilationUnit compilationUnit, IRegion importsRegion) throws JavaModelException {
NavigableMap<Integer, ASTNode> nodesTreeMap = mapTopLevelNodes(compilationUnit);
int surroundingStart;
int positionAfterImports;
if (importsRegion == null) {
PackageDeclaration packageDeclaration = compilationUnit.getPackage();
if (packageDeclaration != null) {
surroundingStart = compilationUnit.getExtendedStartPosition(packageDeclaration)
+ compilationUnit.getExtendedLength(packageDeclaration);
}
else {
surroundingStart = 0;
}
positionAfterImports = surroundingStart;
} else {
Entry<Integer, ASTNode> lowerEntry = nodesTreeMap.lowerEntry(importsRegion.getOffset());
if (lowerEntry != null) {
ASTNode precedingNode = lowerEntry.getValue();
surroundingStart = precedingNode.getStartPosition() + precedingNode.getLength();
} else {
surroundingStart = 0;
}
positionAfterImports = importsRegion.getOffset() + importsRegion.getLength();
}
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<Integer, ASTNode> mapTopLevelNodes(CompilationUnit compilationUnit) {
NavigableMap<Integer, ASTNode> map = new TreeMap<Integer, ASTNode>();
Collection<ASTNode> nodes = new ArrayList<ASTNode>();
if (compilationUnit.getPackage() != null) {
nodes.add(compilationUnit.getPackage());
}
nodes.addAll(compilationUnit.imports());
nodes.addAll(compilationUnit.types());
for (Comment comment : ((List<Comment>) compilationUnit.getCommentList())) {
// Include only top-level (non-doc) comments;
// doc comments are contained within their parent nodes' ranges.
if (comment.getParent() == null) {
nodes.add(comment);
}
}
for (ASTNode node : nodes) {
map.put(node.getStartPosition(), node);
}
return map;
}
/**
* Builds an {@code IdentityHashMap} having the elements of {@code imports} as values and each
* element's {@code importName} as corresponding key. This map can be used to recall the {@code
* ImportEntry} corresponding to a given {@code ImportName} instance even when there are
* duplicate import declarations (where multiple {@code ImportEntry}s have equal, but not
* identical, {@code ImportName}s).
*/
private static Map<ImportName, OriginalImportEntry> mapImportsByNameIdentity(List<OriginalImportEntry> imports) {
Map<ImportName, OriginalImportEntry> importsByName = new IdentityHashMap<ImportName, OriginalImportEntry>();
for (OriginalImportEntry currentImport : imports) {
importsByName.put(currentImport.importName, currentImport);
}
return Collections.unmodifiableMap(importsByName);
}
/**
* Returns a new {@code List} containing those elements of {@code imports} (in their existing
* order) not contained in {@code importsToSubtract}.
*/
private static List<ImportName> subtractImports(
Collection<ImportName> existingImports, Set<ImportName> importsToSubtract) {
List<ImportName> remainingImports = new ArrayList<ImportName>(existingImports.size());
for (ImportName existingImport : existingImports) {
if (!importsToSubtract.contains(existingImport)) {
remainingImports.add(existingImport);
}
}
return remainingImports;
}
private final List<OriginalImportEntry> originalImportEntries;
private final List<ImportName> originalImportsList;
private final Set<ImportName> originalImportsSet;
private final ImportDeclarationWriter importDeclarationWriter;
private final ImportAdder importAdder;
private final Set<ImportName> importsToAdd;
private final Set<ImportName> importsToRemove;
private final boolean reportAllResultantImportsAsCreated;
private final Set<String> typeExplicitSimpleNames;
private final Set<String> staticExplicitSimpleNames;
private final Set<String> implicitImportContainerNames;
private final ConflictIdentifier conflictIdentifier;
private final OnDemandComputer onDemandComputer;
private final Map<ImportName, OriginalImportEntry> importsByNameIdentity;
private final String lineDelimiter;
private final ImportEditor importEditor;
public ImportRewriteAnalyzer(
ICompilationUnit cu,
CompilationUnit astRoot,
ImportRewriteConfiguration configuration) throws JavaModelException {
this.originalImportEntries = Collections.unmodifiableList(readOriginalImports(astRoot));
List<ImportName> importsList = new ArrayList<ImportName>(this.originalImportEntries.size());
Set<ImportName> importsSet = new HashSet<ImportName>();
for (ImportEntry originalImportEntry : this.originalImportEntries) {
ImportName importName = originalImportEntry.importName;
importsList.add(importName);
importsSet.add(importName);
}
this.originalImportsList = Collections.unmodifiableList(importsList);
this.originalImportsSet = Collections.unmodifiableSet(importsSet);
this.importsToAdd = new LinkedHashSet<ImportName>();
this.importsToRemove = new LinkedHashSet<ImportName>();
if (configuration.originalImportHandling.shouldRemoveOriginalImports()) {
this.importsToRemove.addAll(importsSet);
this.reportAllResultantImportsAsCreated = true;
} else {
this.reportAllResultantImportsAsCreated = false;
}
this.typeExplicitSimpleNames = new HashSet<String>();
this.staticExplicitSimpleNames = new HashSet<String>();
ImportGroupComparator importGroupComparator = new ImportGroupComparator(configuration.importOrder);
JavaProject javaProject = (JavaProject) cu.getJavaProject();
this.importAdder = configuration.originalImportHandling.createImportAdder(new ImportComparator(
importGroupComparator,
configuration.typeContainerSorting.createContainerComparator(javaProject),
configuration.staticContainerSorting.createContainerComparator(javaProject)));
this.implicitImportContainerNames =
configuration.implicitImportIdentification.determineImplicitImportContainers(cu);
this.onDemandComputer = new OnDemandComputer(
configuration.typeOnDemandThreshold,
configuration.staticOnDemandThreshold);
this.conflictIdentifier = new ConflictIdentifier(
this.onDemandComputer,
new TypeConflictingSimpleNameFinder(javaProject, new SearchEngine()),
new StaticConflictingSimpleNameFinder(javaProject),
this.implicitImportContainerNames);
this.importsByNameIdentity = mapImportsByNameIdentity(this.originalImportEntries);
this.importDeclarationWriter = new ImportDeclarationWriter(shouldInsertSpaceBeforeSemicolon(javaProject));
this.lineDelimiter = cu.findRecommendedLineSeparator();
this.importEditor = new ImportEditor(
this.lineDelimiter,
configuration.originalImportHandling.shouldFixAllLineDelimiters(),
getBlankLinesBetweenImportGroups(javaProject) + 1,
importGroupComparator,
this.originalImportEntries,
determineRewriteSite(astRoot, this.originalImportEntries),
this.importDeclarationWriter);
}
/**
* Specifies that applying the rewrite should result in the compilation unit containing the
* specified import.
* <p>
* Has no effect if the compilation unit otherwise would contain the given import.
* <p>
* Overrides any previous corresponding call to {@link #removeImport}.
*/
public void addImport(boolean isStatic, String qualifiedName) {
ImportName importToAdd = ImportName.createFor(isStatic, qualifiedName);
this.importsToAdd.add(importToAdd);
this.importsToRemove.remove(importToAdd);
}
/**
* Specifies that applying the rewrite should result in the compilation unit not containing the
* specified import.
* <p>
* Has no effect if the compilation unit otherwise would not contain the given import.
* <p>
* Overrides any previous corresponding call to {@link #addImport}.
*/
public void removeImport(boolean isStatic, String qualifiedName) {
ImportName importToRemove = ImportName.createFor(isStatic, qualifiedName);
this.importsToAdd.remove(importToRemove);
this.importsToRemove.add(importToRemove);
}
/**
* Specifies that any import of the given simple name must be explicit - that it may neither be
* reduced into an on-demand (".*") import nor be filtered as an implicit (e.g. "java.lang.*")
* import.
*/
public void requireExplicitImport(boolean isStatic, String simpleName) {
if (isStatic) {
this.staticExplicitSimpleNames.add(simpleName);
} else {
this.typeExplicitSimpleNames.add(simpleName);
}
}
/**
* Computes and returns the result of performing the rewrite, incorporating all changes
* specified by calls to {@link #addImport}, {@link #removeImport}, and
* {@link #requireExplicitImport}.
* <p>
* This method has no side-effects.
*/
public RewriteResult analyzeRewrite(IProgressMonitor monitor) throws JavaModelException {
List<ImportName> computedImportOrder = computeImportOrder(monitor);
List<ImportEntry> resultingImportEntries = matchExistingOrCreateNew(computedImportOrder);
TextEdit edit = this.importEditor.createTextEdit(resultingImportEntries);
Set<ImportName> createdImports = new HashSet<ImportName>(computedImportOrder);
if (!this.reportAllResultantImportsAsCreated) {
createdImports.removeAll(this.originalImportsSet);
}
return new RewriteResult(edit, createdImports);
}
private List<ImportName> computeImportOrder(IProgressMonitor progressMonitor) throws JavaModelException {
Set<ImportName> importsWithAdditionsAndRemovals = new HashSet<ImportName>(this.originalImportsSet);
importsWithAdditionsAndRemovals.addAll(this.importsToAdd);
importsWithAdditionsAndRemovals.removeAll(this.importsToRemove);
Set<ImportName> touchedContainers = determineTouchedContainers();
Conflicts conflicts = this.conflictIdentifier.identifyConflicts(
importsWithAdditionsAndRemovals,
touchedContainers,
this.typeExplicitSimpleNames,
this.staticExplicitSimpleNames,
progressMonitor);
Set<String> allTypeExplicitSimpleNames = new HashSet<String>(this.typeExplicitSimpleNames);
allTypeExplicitSimpleNames.addAll(conflicts.typeConflicts);
Set<String> allStaticExplicitSimpleNames = new HashSet<String>(this.staticExplicitSimpleNames);
allStaticExplicitSimpleNames.addAll(conflicts.staticConflicts);
Set<ImportName> implicitImports = identifyImplicitImports(this.importsToAdd, allTypeExplicitSimpleNames);
List<ImportName> importsWithoutImplicits =
subtractImports(importsWithAdditionsAndRemovals, implicitImports);
Collection<OnDemandReduction> onDemandReductions = this.onDemandComputer.identifyPossibleReductions(
new HashSet<ImportName>(importsWithoutImplicits),
touchedContainers,
allTypeExplicitSimpleNames,
allStaticExplicitSimpleNames);
ImportsDelta delta = computeDelta(implicitImports, onDemandReductions);
List<ImportName> importsWithRemovals = subtractImports(this.originalImportsList, delta.importsToRemove);
List<ImportName> importsWithAdditions = this.importAdder.addImports(importsWithRemovals, delta.importsToAdd);
return importsWithAdditions;
}
private Set<ImportName> determineTouchedContainers() {
Collection<ImportName> touchedContainers = new ArrayList<ImportName>(
this.importsToAdd.size() + this.importsToRemove.size());
for (ImportName addedImport : this.importsToAdd) {
touchedContainers.add(addedImport.getContainerOnDemand());
}
for (ImportName removedImport : this.importsToRemove) {
touchedContainers.add(removedImport.getContainerOnDemand());
}
return Collections.unmodifiableSet(new HashSet<ImportName>(touchedContainers));
}
private Set<ImportName> identifyImplicitImports(
Collection<ImportName> addedImports, Set<String> allTypeExplicitSimpleNames) {
if (this.implicitImportContainerNames.isEmpty()) {
return Collections.emptySet();
}
Collection<ImportName> implicits = new ArrayList<ImportName>(addedImports.size());
for (ImportName addedImport : addedImports) {
boolean isImplicit = this.implicitImportContainerNames.contains(addedImport.containerName)
&& !allTypeExplicitSimpleNames.contains(addedImport.simpleName);
if (isImplicit) {
implicits.add(addedImport);
}
}
if (implicits.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<ImportName>(implicits);
}
private List<ImportEntry> matchExistingOrCreateNew(Collection<ImportName> importNames) {
List<ImportEntry> importEntries = new ArrayList<ImportEntry>(importNames.size());
for (ImportName importName : importNames) {
ImportEntry importEntry = this.importsByNameIdentity.get(importName);
if (importEntry == null) {
importEntry = new NewImportEntry(importName);
}
importEntries.add(importEntry);
}
return importEntries;
}
private ImportsDelta computeDelta(
Collection<ImportName> implicitImports, Collection<OnDemandReduction> onDemandReductions) {
Collection<ImportName> additions = new ArrayList<ImportName>(this.originalImportsList.size());
additions.addAll(this.importsToAdd);
Collection<ImportName> removals = new ArrayList<ImportName>(this.originalImportsList.size());
removals.addAll(this.importsToRemove);
removals.addAll(implicitImports);
additions.removeAll(removals);
for (OnDemandReduction onDemandReduction : onDemandReductions) {
additions.removeAll(onDemandReduction.reducibleImports);
removals.addAll(onDemandReduction.reducibleImports);
additions.add(onDemandReduction.containerOnDemand);
removals.remove(onDemandReduction.containerOnDemand);
}
return new ImportsDelta(additions, removals);
}
}