/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.duplications.detector.original;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.sonar.duplications.detector.ContainsInComparator;
import org.sonar.duplications.index.CloneGroup;
import org.sonar.duplications.index.ClonePart;
import org.sonar.duplications.utils.SortedListsUtils;
/**
* Performs incremental and brute force algorithm in order to filter clones, which are fully covered by other clones.
* All clones for filtering must be of the same origin - there is no sanity check on this.
* In a worst case it performs O(N*N) comparisons.
* <p>
* Godin: This implementation was chosen because it simple.
* And I wasn't able to find big difference in performance with an interval tree, which we had for the moment of testing.
* Whereas in fact I expected that interval tree would be better for this task.
* Moreover with interval tree we also can use incremental approach,
* but we did not had an implementation with remove operation for the moment of testing.
* </p>
*/
final class Filter {
/**
* Note that LinkedList should provide better performance here, because of use of operation remove.
*
* @see #add(CloneGroup)
*/
private final List<CloneGroup> filtered = new LinkedList<>();
/**
* @return current results of filtering
*/
public List<CloneGroup> getResult() {
return filtered;
}
/**
* Running time - O(N*2*C), where N - number of clones, which was found earlier and C - time of {@link #containsIn(CloneGroup, CloneGroup)}.
*/
public void add(CloneGroup current) {
Iterator<CloneGroup> i = filtered.iterator();
while (i.hasNext()) {
CloneGroup earlier = i.next();
// Note that following two conditions cannot be true together - proof by contradiction:
// let C be the current clone and A and B were found earlier
// then since relation is transitive - (A in C) and (C in B) => (A in B)
// so A should be filtered earlier
if (Filter.containsIn(current, earlier)) {
// current clone fully covered by clone, which was found earlier
return;
}
if (Filter.containsIn(earlier, current)) {
// current clone fully covers clone, which was found earlier
i.remove();
}
}
filtered.add(current);
}
/**
* Checks that second clone contains first one.
* <p>
* Clone A is contained in another clone B, if every part pA from A has part pB in B,
* which satisfy the conditions:
* <pre>
* (pA.resourceId == pB.resourceId) and (pB.unitStart <= pA.unitStart) and (pA.unitEnd <= pB.unitEnd)
* </pre>
* And all resourcesId from B exactly the same as all resourceId from A, which means that also every part pB from B has part pA in A,
* which satisfy the condition:
* <pre>
* pB.resourceId == pA.resourceId
* </pre>
* So this relation is:
* <ul>
* <li>reflexive - A in A</li>
* <li>transitive - (A in B) and (B in C) => (A in C)</li>
* <li>antisymmetric - (A in B) and (B in A) <=> (A = B)</li>
* </ul>
* </p>
* <p>
* <strong>Important: this method relies on fact that all parts were already sorted by resourceId and unitStart by using
* {@link BlocksGroup.BlockComparator}, which uses {@link org.sonar.duplications.utils.FastStringComparator} for comparison by resourceId.</strong>
* </p>
* <p>
* Running time - O(|A|+|B|).
* </p>
*/
static boolean containsIn(CloneGroup first, CloneGroup second) {
if (first.getCloneUnitLength() > second.getCloneUnitLength()) {
return false;
}
List<ClonePart> firstParts = first.getCloneParts();
List<ClonePart> secondParts = second.getCloneParts();
return SortedListsUtils.contains(secondParts, firstParts, new ContainsInComparator(second.getCloneUnitLength(), first.getCloneUnitLength()))
&& SortedListsUtils.contains(firstParts, secondParts, ContainsInComparator.RESOURCE_ID_COMPARATOR);
}
}