/** * 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.io.PrintStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import de.fosd.jdime.config.merge.MergeScenario; import de.fosd.jdime.config.merge.Revision; import de.fosd.jdime.matcher.matching.Matching; import de.fosd.jdime.stats.parser.ParseResult; import de.fosd.jdime.stats.parser.Parser; /** * A collection of statistics values about a <code>MergeScenario</code>. */ public class MergeScenarioStatistics { private MergeScenario<?> mergeScenario; private Set<Matching<?>> matchings; private Map<Revision, Map<KeyEnums.Level, ElementStatistics>> levelStatistics; private Map<Revision, Map<KeyEnums.Type, ElementStatistics>> typeStatistics; private Map<Revision, MergeStatistics> mergeStatistics; private ElementStatistics lineStatistics; private ElementStatistics fileStatistics; private ElementStatistics directoryStatistics; private int conflicts; private long runtime; /** * Constructs a new <code>MergeScenarioStatistics</code> object for the given <code>MergeScenario</code>. * * @param mergeScenario * the <code>MergeScenario</code> this <code>MergeScenarioStatistics</code> collects statistics for */ public MergeScenarioStatistics(MergeScenario<?> mergeScenario) { this.mergeScenario = mergeScenario; this.matchings = new HashSet<>(); this.levelStatistics = new HashMap<>(); this.typeStatistics = new HashMap<>(); this.mergeStatistics = new HashMap<>(); this.lineStatistics = new ElementStatistics(); this.fileStatistics = new ElementStatistics(); this.directoryStatistics = new ElementStatistics(); this.conflicts = 0; this.runtime = 0; } /** * Copy constructor. * * @param toCopy * the <code>MergeScenarioStatistics</code> to copy */ public MergeScenarioStatistics(MergeScenarioStatistics toCopy) { this.mergeScenario = new MergeScenario<>(toCopy.mergeScenario); this.matchings = new HashSet<>(); for (Matching<?> matching : toCopy.matchings) { this.matchings.add(new Matching<>(matching)); } this.levelStatistics = new HashMap<>(); for (Map.Entry<Revision, Map<KeyEnums.Level, ElementStatistics>> entry : toCopy.levelStatistics.entrySet()) { Map<KeyEnums.Level, ElementStatistics> map = new HashMap<>(); for (Map.Entry<KeyEnums.Level, ElementStatistics> subEntry : entry.getValue().entrySet()) { map.put(subEntry.getKey(), new ElementStatistics(subEntry.getValue())); } this.levelStatistics.put(entry.getKey(), map); } this.typeStatistics = new HashMap<>(); for (Map.Entry<Revision, Map<KeyEnums.Type, ElementStatistics>> entry : toCopy.typeStatistics.entrySet()) { Map<KeyEnums.Type, ElementStatistics> map = new HashMap<>(); for (Map.Entry<KeyEnums.Type, ElementStatistics> subEntry : entry.getValue().entrySet()) { map.put(subEntry.getKey(), new ElementStatistics(subEntry.getValue())); } this.typeStatistics.put(entry.getKey(), map); } this.mergeStatistics = new HashMap<>(); for (Map.Entry<Revision, MergeStatistics> entry : toCopy.mergeStatistics.entrySet()) { this.mergeStatistics.put(entry.getKey(), new MergeStatistics(entry.getValue())); } this.lineStatistics = new ElementStatistics(toCopy.lineStatistics); this.fileStatistics = new ElementStatistics(toCopy.fileStatistics); this.directoryStatistics = new ElementStatistics(toCopy.directoryStatistics); this.conflicts = toCopy.conflicts; this.runtime = toCopy.runtime; } /** * Returns the <code>MergeScenario</code> this <code>MergeScenarioStatistics</code> collects statistics for. * * @return the <code>MergeScenario</code> */ public MergeScenario<?> getMergeScenario() { return mergeScenario; } /** * Adds a <code>Matching</code> to this <code>MergeScenarioStatistics</code>. * * @param matching * the <code>Matching</code> to add */ public void addMatching(Matching<?> matching) { matchings.add(matching); } /** * Adds all <code>Matching</code>s contained in <code>matchings</code> to this <code>MergeScenarioStatistics</code>. * * @param matchings * the <code>Matching</code>s to add */ public void addAllMatchings(Collection<? extends Matching<?>> matchings) { this.matchings.addAll(matchings); } /** * Checks whether an <code>ElementStatistics</code> was registered for the <code>Revision</code> and * <code>KeyEnums.Level</code> combination. * * @param rev * the <code>Revision</code> to check for * @param level * the <code>KeyEnums.Level</code> to check for * @return true iff an <code>ElementStatistics</code> was registered */ public boolean containsLevelStatistics(Revision rev, KeyEnums.Level level) { return levelStatistics.containsKey(rev) && levelStatistics.get(rev).containsKey(level); } /** * Returns the statistics container for the different <code>KeyEnums.Level</code> values. * * @return the <code>Map</code> from the <code>Revision</code>s of the <code>MergeScenario</code> to the statistics * collected for different <code>KeyEnums.Level</code>s */ public Map<Revision, Map<KeyEnums.Level, ElementStatistics>> getLevelStatistics() { return levelStatistics; } /** * Returns the <code>ElementStatistics</code> for the given <code>Revision</code> and <code>LEVEL</code>. * Creates and registers a new <code>ElementStatistics</code> object if necessary. * * @param rev * the <code>Revision</code> to look up * @param level * the <code>LEVEL</code> in the <code>Revision</code> * @return the corresponding <code>ElementStatistics</code> */ public ElementStatistics getLevelStatistics(Revision rev, KeyEnums.Level level) { return levelStatistics.computeIfAbsent(rev, r -> new HashMap<>()).computeIfAbsent(level, l -> new ElementStatistics()); } /** * Checks whether an <code>ElementStatistics</code> was registered for the <code>Revision</code> and * <code>KeyEnums.Type</code> combination. * * @param rev * the <code>Revision</code> to check for * @param type * the <code>KeyEnums.Type</code> to check for * @return true iff an <code>ElementStatistics</code> was registered */ public boolean containsTypeStatistics(Revision rev, KeyEnums.Type type) { return typeStatistics.containsKey(rev) && typeStatistics.get(rev).containsKey(type); } /** * Returns the statistics container for the different <code>KeyEnums.Type</code> values. * * @return the <code>Map</code> from the <code>Revision</code>s of the <code>MergeScenario</code> to the statistics * collected for different <code>KeyEnums.Type</code>s */ public Map<Revision, Map<KeyEnums.Type, ElementStatistics>> getTypeStatistics() { return typeStatistics; } /** * Returns the <code>ElementStatistics</code> for the given <code>Revision</code> and <code>TYPE</code>. * Creates and registers a new <code>ElementStatistics</code> object if necessary. If <code>type</code> is * one of <code>LINE, FILE or DIRECTORY</code> the <code>Revision</code> is ignored the appropriate * <code>getXStatistics</code> method is used. * * @param rev * the <code>Revision</code> to look up * @param type * the <code>TYPE</code> in the <code>Revision</code> * @return the corresponding <code>ElementStatistics</code> */ public ElementStatistics getTypeStatistics(Revision rev, KeyEnums.Type type) { switch (type) { case LINE: return lineStatistics; case FILE: return fileStatistics; case DIRECTORY: return directoryStatistics; } return typeStatistics.computeIfAbsent(rev, r -> new HashMap<>()).computeIfAbsent(type, l -> new ElementStatistics()); } /** * Returns the statistics container for the <code>MergeStatistics</code> of the <code>Revision</code>s of the * <code>MergeScenario</code>. * * @return the <code>Map</code> from the <code>Revision</code>s of the <code>MergeScenario</code> to the * <code>MergeStatistics</code> collected for them */ public Map<Revision, MergeStatistics> getMergeStatistics() { return mergeStatistics; } /** * Returns the <code>MergeStatistics</code> collected for the given <code>Revision</code>. A new * <code>MergeStatistics</code> object will be created of necessary. * * @param rev * the <code>Revision</code> to get the <code>MergeStatistics</code> for * @return the corresponding <code>MergeStatistics</code> */ public MergeStatistics getMergeStatistics(Revision rev) { return mergeStatistics.computeIfAbsent(rev, r -> new MergeStatistics()); } /** * Returns statistics for {@link KeyEnums.Type#LINE}. * * @return the line statistics */ public ElementStatistics getLineStatistics() { return lineStatistics; } /** * Parses the given <code>mergeResult</code> using {@link Parser#parse(String)} and adds the resulting statistics * to this <code>MergeScenarioStatistics</code>. * * @param mergeResult * the code to parse * @return the <code>ParseResult</code> from {@link Parser#parse(String)} */ public ParseResult addLineStatistics(String mergeResult) { ParseResult result = Parser.parse(mergeResult); lineStatistics.incrementTotal(result.getLinesOfCode()); lineStatistics.incrementNumOccurInConflic(result.getConflictingLinesOfCode()); conflicts += result.getConflicts(); return result; } /** * Parses the given <code>mergeResult</code> using {@link Parser#parse(String)} and sets the resulting statistics * to this <code>MergeScenarioStatistics</code>. * * @param mergeResult * the code to parse * @return the <code>ParseResult</code> from {@link Parser#parse(String)} */ public ParseResult setLineStatistics(String mergeResult) { ParseResult result = Parser.parse(mergeResult); lineStatistics.setTotal(result.getLinesOfCode()); lineStatistics.setNumOccurInConflict(result.getConflictingLinesOfCode()); conflicts = result.getConflicts(); return result; } /** * Returns the statistics for {@link KeyEnums.Type#FILE}. * * @return the file statistics */ public ElementStatistics getFileStatistics() { return fileStatistics; } /** * Returns the statistics for {@link KeyEnums.Type#DIRECTORY}. * * @return the directory statistics */ public ElementStatistics getDirectoryStatistics() { return directoryStatistics; } /** * Returns the number conflicts. * * @return the number of conflicts */ public int getConflicts() { return conflicts; } /** * Returns the runtime. * * @return the runtime */ public long getRuntime() { return runtime; } /** * Sets the runtime to the new value. * * @param runtime * the new runtime */ public void setRuntime(long runtime) { this.runtime = runtime; } /** * Adds all <code>ElementStatistics</code> in <code>other</code> to the corresponding * <code>ElementStatistics</code> added to <code>this</code>. If an <code>ElementStatistics</code> in * <code>other</code> has no partner in <code>this</code> it will simply be added to <code>this</code>. * * @param other * the <code>MergeScenarioStatistics</code> to add to <code>this</code> * @see ElementStatistics#add(ElementStatistics) */ public void add(MergeScenarioStatistics other) { addAllMatchings(other.matchings); for (Map.Entry<Revision, Map<KeyEnums.Level, ElementStatistics>> entry : other.levelStatistics.entrySet()) { Revision rev = entry.getKey(); for (Map.Entry<KeyEnums.Level, ElementStatistics> subEntry : entry.getValue().entrySet()) { KeyEnums.Level level = subEntry.getKey(); getLevelStatistics(rev, level).add(subEntry.getValue()); } } for (Map.Entry<Revision, Map<KeyEnums.Type, ElementStatistics>> entry : other.typeStatistics.entrySet()) { Revision rev = entry.getKey(); for (Map.Entry<KeyEnums.Type, ElementStatistics> subEntry : entry.getValue().entrySet()) { KeyEnums.Type type = subEntry.getKey(); getTypeStatistics(rev, type).add(subEntry.getValue()); } } for (Map.Entry<Revision, MergeStatistics> entry : other.mergeStatistics.entrySet()) { getMergeStatistics(entry.getKey()).add(entry.getValue()); } lineStatistics.add(other.lineStatistics); fileStatistics.add(other.fileStatistics); directoryStatistics.add(other.directoryStatistics); conflicts += other.conflicts; runtime += other.runtime; } /** * Writes a human readable representation of this <code>MergeScenarioStatistics</code> object to the given * <code>PrintStream</code>. * * @param os * the <code>PrintStream</code> to write to */ public void print(PrintStream os) { String indent = " "; os.printf("%s for %s:%n", MergeScenarioStatistics.class.getSimpleName(), MergeScenario.class.getSimpleName()); mergeScenario.asList().forEach(artifact -> os.printf("%s%s%n", indent, artifact.getId())); os.println("General:"); os.printf("%sConflicts: %s%n", indent, conflicts); os.printf("%sRuntime: %dms%n", indent, runtime); if (!matchings.isEmpty()) os.println("Matchings"); matchings.stream().sorted().forEachOrdered(matching -> { os.printf("%s%s%n", indent, matching); }); if (!levelStatistics.isEmpty()) os.println("Level Statistics"); levelStatistics.forEach((rev, map) -> map.forEach((level, stats) -> { os.printf("%s %s %s %s%n", Revision.class.getSimpleName(), rev, KeyEnums.Level.class.getSimpleName(), level); stats.print(os, indent); })); if (!typeStatistics.isEmpty()) os.println("Type Statistics"); typeStatistics.forEach((rev, map) -> map.forEach((type, stats) -> { os.printf("%s %s %s %s%n", Revision.class.getSimpleName(), rev, KeyEnums.Type.class.getSimpleName(), type); stats.print(os, indent); })); if (!mergeStatistics.isEmpty()) os.println("Merge Statistics"); mergeStatistics.forEach((rev, stats) -> { os.printf("%s %s%n", Revision.class.getSimpleName(), rev); stats.print(os, indent); }); os.println("Line Statistics"); lineStatistics.print(os, indent); os.println("File Statistics"); fileStatistics.print(os, indent); os.println("Directory Statistics"); directoryStatistics.print(os, indent); } }