/** * Copyright (C) 2013-2014 Olaf Lessenich * Copyright (C) 2014-2015 University of Passau, Germany * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * Contributors: * Olaf Lessenich <lessenic@fim.uni-passau.de> * Georg Seibt <seibt@fim.uni-passau.de> */ package de.fosd.jdime.stats; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.IntSummaryStatistics; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import de.fosd.jdime.artifact.Artifact; import de.fosd.jdime.config.merge.MergeContext; import de.fosd.jdime.config.merge.MergeScenario; import de.fosd.jdime.config.merge.Revision; import de.fosd.jdime.matcher.matching.Matching; public interface StatisticsInterface { /** * Returns the <code>KeyEnums.Type</code> matching this <code>Artifact</code>. * * @return the type of this <code>Artifact</code> */ KeyEnums.Type getType(); /** * Returns the <code>KeyEnums.Level</code> matching this <code>Artifact</code>. * * @return the level of this <code>Artifact</code> */ KeyEnums.Level getLevel(); /** * Called by the <code>AddOperation</code> on the <code>Artifact</code> being added. The default implementation * does nothing and is intended to be overridden by <code>Artifact</code> implementations wishing to record custom * statistics after being added. * * @param mScenarioStatistics * the <code>MergeScenarioStatistics</code> for the current * <code>MergeScenarioStatistics</code> * @param mergeContext * the <code>MergeContext</code> of the current run */ default void addOpStatistics(MergeScenarioStatistics mScenarioStatistics, MergeContext mergeContext) { } /** * Called by the <code>DeleteOperation</code> on the <code>Artifact</code> being deleted. The default implementation * does nothing and is intended to be overridden by <code>Artifact</code> implementations wishing to record custom * statistics after being deleted. * * @param mScenarioStatistics * the <code>MergeScenarioStatistics</code> for the current * <code>MergeScenarioStatistics</code> * @param mergeContext * the <code>MergeContext</code> of the current run */ default void deleteOpStatistics(MergeScenarioStatistics mScenarioStatistics, MergeContext mergeContext) { } /** * Called by the <code>MergeOperation</code> on the first <code>Artifact</code> that was added to the * <code>MergeScenario</code> being merged. The default implementation does nothing and is intended to be * overridden by <code>Artifact</code> implementations wishing to record custom statistics after being merged. * * @param mScenarioStatistics * the <code>MergeScenarioStatistics</code> for the current * <code>MergeScenarioStatistics</code> * @param mergeContext * the <code>MergeContext</code> of the current run */ default void mergeOpStatistics(MergeScenarioStatistics mScenarioStatistics, MergeContext mergeContext) { } /** * Collects statistics about the given <code>artifact</code> tree. The <code>otherRev</code> is used when counting * added, deleted and matched elements elements. <code>otherRev</code> should be the 'opposite' * <code>Revision</code> e.g. when <code>artifact</code> represents the tree of the 'left' revision, * <code>otherRev</code> should be the 'right' <code>Revision</code>. The resulting * <code>MergeScenarioStatistics</code> contains <code>null</code> as its <code>MergeScenario</code> and is intended * to be added to an existing <code>MergeScenarioStatistics</code> instance. * * @param artifact * the <code>Artifact</code> tree to collect statistics for * @param otherRev * the 'opposite' <code>Revision</code> or <code>null</code> (when collecting statistics for the target * <code>Artifact</code>) * @return the resulting <code>MergeScenarioStatistics</code> */ static MergeScenarioStatistics getASTStatistics(Artifact<?> artifact, Revision otherRev) { MergeScenarioStatistics statistics = new MergeScenarioStatistics((MergeScenario<?>) null); List<ElementStatistics> elementStats = new ArrayList<>(); List<Artifact<?>> preOrder = new ArrayList<>(); { Deque<Artifact<?>> q = new ArrayDeque<>(Collections.singleton(artifact)); while (!q.isEmpty()) { Artifact<?> curr = q.removeFirst(); preOrder.add(curr); curr.getChildren().forEach(q::addFirst); } } Predicate<Artifact<?>> otherMatches = a -> ((otherRev == null && a.hasMatches()) || a.hasMatching(otherRev)); Predicate<Artifact<?>> isConflict = Artifact::isConflict; for (Artifact<?> current : preOrder) { elementStats.clear(); KeyEnums.Type type = current.getType(); KeyEnums.Level level = current.getLevel(); elementStats.add(statistics.getTypeStatistics(current.getRevision(), type)); if (level != KeyEnums.Level.NONE) { elementStats.add(statistics.getLevelStatistics(current.getRevision(), level)); } elementStats.forEach(ElementStatistics::incrementTotal); if (current.isConflict()) { elementStats.forEach(ElementStatistics::incrementNumOccurInConflic); } else if (otherMatches.negate().test(current)) { // added or deleted? if (current.hasMatches()) { elementStats.forEach(ElementStatistics::incrementNumDeleted); } else { elementStats.forEach(ElementStatistics::incrementNumAdded); } } if (otherRev != null) { Matching<?> matching = current.getMatching(otherRev); if (matching != null) { statistics.addMatching(matching); } } } MergeStatistics mergeStatistics = statistics.getMergeStatistics(artifact.getRevision()); Optional<Artifact<?>> max = preOrder.stream().max((o1, o2) -> Integer.compare(o1.getNumChildren(), o2.getNumChildren())); max.ifPresent(a -> mergeStatistics.setMaxNumChildren(a.getNumChildren())); mergeStatistics.setMaxASTDepth(artifact.getMaxDepth()); IntSummaryStatistics summary = segmentStatistics(preOrder, isConflict.or(otherMatches.negate())); mergeStatistics.setNumChunks((int) summary.getCount()); mergeStatistics.setAvgChunkSize((float) summary.getAverage()); return statistics; } /** * Given a collection of items and a <code>Predicate</code> to be fulfilled returns an * <code>IntSummaryStatistics</code> over a list of the lengths of segments of successive (as determined by the * order they are returned by the iterator of the collection) items that fulfill the predicate. * * @param coll * the collection of items * @param test * the predicate to fulfill * @param <T> * the type of the items * @return an <code>IntSummaryStatistics</code> over the list of segment lengths */ static <T> IntSummaryStatistics segmentStatistics(Collection<T> coll, Predicate<T> test) { List<Integer> chunkSizes = new ArrayList<>(); int currentSize = 0; for (T item : coll) { boolean p = test.test(item); if (p) { currentSize++; } else { if (currentSize != 0) { chunkSizes.add(currentSize); currentSize = 0; } } } if (currentSize != 0) { chunkSizes.add(currentSize); } return chunkSizes.stream().collect(Collectors.summarizingInt(i -> i)); } }