/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.apply;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
import com.google.errorprone.DescriptionListener;
import com.google.errorprone.ErrorProneEndPosMap;
import com.google.errorprone.JDKCompatible;
import com.google.errorprone.fixes.Replacement;
import com.google.errorprone.matchers.Description;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Implementation of a {@link Diff} that performs the modifications that are passed to its
* {@link #onDescribed} method, with no formatting.
*
* <p>If imports are changed, they are resorted as per Google Java style.
*
* @author lowasser@google.com (Louis Wasserman)
*/
public final class DescriptionBasedDiff implements DescriptionListener, Diff {
private final String sourcePath;
private final JCCompilationUnit compilationUnit;
private final Set<String> importsToAdd;
private final Set<String> importsToRemove;
private final ErrorProneEndPosMap endPosMap;
private final RangeMap<Integer, Replacement> replacements;
public static DescriptionBasedDiff create(JCCompilationUnit compilationUnit) {
return new DescriptionBasedDiff(compilationUnit);
}
private DescriptionBasedDiff(JCCompilationUnit compilationUnit) {
this.compilationUnit = checkNotNull(compilationUnit);
this.sourcePath = compilationUnit.getSourceFile().toUri().getPath();
this.importsToAdd = new HashSet<>();
this.importsToRemove = new HashSet<>();
this.endPosMap = JDKCompatible.getEndPosMap(compilationUnit);
this.replacements = TreeRangeMap.create();
}
@Override
public String getRelevantFileName() {
return sourcePath;
}
@Override
public void onDescribed(Description description) {
importsToAdd.addAll(description.suggestedFix.getImportsToAdd());
importsToRemove.removeAll(description.suggestedFix.getImportsToRemove());
for (Replacement replacement : description.suggestedFix.getReplacements(endPosMap)) {
addReplacement(replacement);
}
}
private void addReplacement(Replacement replacement) {
checkNotNull(replacement);
Range<Integer> range = Range.closedOpen(replacement.startPosition, replacement.endPosition);
RangeMap<Integer, Replacement> overlaps = replacements.subRangeMap(range);
checkArgument(overlaps.asMapOfRanges().isEmpty(), "Replacement %s overlaps with %s",
replacement, overlaps);
replacements.put(range, replacement);
}
@Override
public void applyDifferences(SourceFile sourceFile) throws DiffNotApplicableException {
/*
* We want to apply replacements in reverse order of start position, and we know that imports
* come before all the other replacements.
*/
List<Replacement> replacementsInOrder = new ArrayList<>(replacements.asMapOfRanges().values());
Collections.reverse(replacementsInOrder);
if (!importsToAdd.isEmpty() || !importsToRemove.isEmpty()) {
ImportStatements importStatements = ImportStatements.create(compilationUnit);
importStatements.addAll(importsToAdd);
importStatements.removeAll(importsToRemove);
replacementsInOrder.add(new Replacement(importStatements.getStartPos(),
importStatements.getEndPos(), importStatements.toString()));
}
for (Replacement replacement : replacementsInOrder) {
sourceFile.replaceChars(replacement.startPosition, replacement.endPosition,
replacement.replaceWith);
}
}
}