/* * Copyright 2016 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.fixes; import static com.google.common.base.Preconditions.checkArgument; import com.google.common.base.Joiner; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; import com.google.common.collect.Range; import com.google.common.collect.RangeMap; import com.google.common.collect.TreeRangeMap; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeMap; /** A collection of {@link Replacement}s to be made to a source file. */ public class Replacements { /** * We apply replacements in reverse order of start position, so that replacements that * change the length of the input don't affect the position of earlier replacements. */ private static final Comparator<Range<Integer>> DESCENDING = new Comparator<Range<Integer>>() { @Override public int compare(Range<Integer> o1, Range<Integer> o2) { return ComparisonChain.start() .compare(o1.lowerEndpoint(), o2.lowerEndpoint(), Ordering.natural().reverse()) .compare(o1.upperEndpoint(), o2.upperEndpoint(), Ordering.natural().reverse()) .result(); } }; private final TreeMap<Range<Integer>, Replacement> replacements = new TreeMap<>(DESCENDING); private final RangeMap<Integer, Replacement> overlaps = TreeRangeMap.create(); public Replacements add(Replacement replacement) { if (replacements.containsKey(replacement.range())) { Replacement existing = replacements.get(replacement.range()); if (!existing.equals(replacement)) { if (replacement.range().isEmpty()) { // The replacement is an insertion, and there's an existing insertion at the same point. // In that case, we coalesce the additional insertion with the existing one. replacement = Replacement.create( existing.startPosition(), existing.endPosition(), existing.replaceWith() + replacement.replaceWith()); } else { throw new IllegalArgumentException( String.format("%s conflicts with existing replacement %s", replacement, existing)); } } } else { checkOverlaps(replacement); } replacements.put(replacement.range(), replacement); return this; } private void checkOverlaps(Replacement replacement) { Collection<Replacement> overlap = overlaps.subRangeMap(replacement.range()).asMapOfRanges().values(); checkArgument( overlap.isEmpty(), "%s overlaps with existing replacements: %s", replacement, Joiner.on(", ").join(overlap)); overlaps.put(replacement.range(), replacement); } /** Non-overlapping replacements, sorted in descending order by position. */ public Set<Replacement> descending() { // TODO(cushon): refactor SuggestedFix#getReplacements and just return a Collection, return new LinkedHashSet<>(replacements.values()); } public boolean isEmpty() { return replacements.isEmpty(); } }