/** * 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.operations; import java.io.IOException; import java.util.Map; import java.util.Objects; import java.util.logging.Logger; import de.fosd.jdime.artifact.Artifact; import de.fosd.jdime.artifact.ArtifactList; import de.fosd.jdime.config.merge.MergeContext; import de.fosd.jdime.config.merge.MergeScenario; import de.fosd.jdime.config.merge.MergeType; import de.fosd.jdime.stats.MergeScenarioStatistics; import de.fosd.jdime.stats.Statistics; import static de.fosd.jdime.config.merge.MergeScenario.BASE; import static de.fosd.jdime.stats.KeyEnums.Type.DIRECTORY; import static de.fosd.jdime.stats.KeyEnums.Type.FILE; /** * The operation merges <code>Artifact</code>s. * * @param <T> * type of artifact * * @author Olaf Lessenich */ public class MergeOperation<T extends Artifact<T>> extends Operation<T> { private static final Logger LOG = Logger.getLogger(MergeOperation.class.getCanonicalName()); /** * The <code>MergeScenario</code> containing the <code>Artifact</code>s to be merged. */ private MergeScenario<T> mergeScenario; /** * The <code>Artifact</code> to output the result of the merge to. */ private T target; private boolean nway = false; private String leftCondition; private String rightCondition; /** * Constructs a new <code>MergeOperation</code> merging the given <code>inputArtifacts</code>. The result * will be output into <code>target</code> if output is enabled. <code>inputArtifacts</code> may not be * <code>null</code>. <br><br> * * <code>inputArtifacts</code> must have either two or three elements which will be interpreted as * [LeftArtifact, (BaseArtifact,) RightArtifact]. A two-way-merge will be performed for a list of length 2, a * three-way-merge for one of length three. * * @param inputArtifacts * the input artifacts * @param target * the output artifact * @param leftCondition condition for left alternative * @param rightCondition condition for right alternative * * @param nway * @throws IllegalArgumentException * if the size of <code>inputArtifacts</code> is invalid * @throws IllegalArgumentException * if the artifacts in <code>inputArtifacts</code> produce an invalid <code>MergeScenario</code> according to * {@link MergeScenario#isValid()} * @throws IOException * if the dummy file used as BaseArtifact in a two-way-merge can not be created */ public MergeOperation(ArtifactList<T> inputArtifacts, T target, String leftCondition, String rightCondition, boolean nway) throws IOException { Objects.requireNonNull(inputArtifacts, "inputArtifacts must not be null!"); this.target = target; MergeType mergeType; T left, base, right; int numArtifacts = inputArtifacts.size(); if (numArtifacts < MergeType.MINFILES) { String msg = String.format("Invalid number of artifacts (%d) for a MergeOperation.", numArtifacts); throw new IllegalArgumentException(msg); } if (nway) { this.mergeScenario = new MergeScenario<>(inputArtifacts); LOG.finest("Created N-way scenario"); } else { if (numArtifacts == MergeType.TWOWAY_FILES) { left = inputArtifacts.get(0); base = left.createEmptyArtifact(BASE); right = inputArtifacts.get(1); mergeType = MergeType.TWOWAY; LOG.finest("Created TWO-way scenario"); } else if (numArtifacts == MergeType.THREEWAY_FILES) { left = inputArtifacts.get(0); base = inputArtifacts.get(1); right = inputArtifacts.get(2); mergeType = MergeType.THREEWAY; LOG.finest("Created THREE-way scenario"); } else { String msg = String.format("Invalid number of artifacts (%d) for a MergeOperation.", numArtifacts); throw new IllegalArgumentException(msg); } this.mergeScenario = new MergeScenario<>(mergeType, left, base, right); if (!mergeScenario.isValid()) { throw new IllegalArgumentException("The artifacts in inputArtifacts produced an invalid MergeScenario."); } } if (leftCondition != null || rightCondition != null) { this.leftCondition = leftCondition; this.rightCondition = rightCondition; } } /** * Constructs a new <code>MergeOperation</code> using the given <code>mergeScenario</code> and <code>target</code>. * <code>mergeScenario</code> may be <code>null</code>. * * @param mergeScenario * the <code>Artifact</code>s to be merged * @param target * the output <code>Artifact</code> * @param leftCondition condition for left alternative * @param rightCondition condition for right alternative * * @throws IllegalArgumentException * if <code>mergeScenario</code> is invalid */ public MergeOperation(MergeScenario<T> mergeScenario, T target, String leftCondition, String rightCondition) { Objects.requireNonNull(mergeScenario, "mergeScenario must not be null!"); if (!mergeScenario.isValid()) { throw new IllegalArgumentException("mergeScenario is invalid."); } this.mergeScenario = mergeScenario; this.target = target; if (leftCondition != null || rightCondition != null) { this.leftCondition = leftCondition; this.rightCondition = rightCondition; } } @Override public void apply(MergeContext context) { if (!context.isConditionalMerge(mergeScenario.getLeft())) { assert (mergeScenario.getLeft().exists()) : "Left artifact does not exist: " + mergeScenario.getLeft(); assert (mergeScenario.getRight().exists()) : "Right artifact does not exist: " + mergeScenario.getRight(); assert (mergeScenario.getBase().isEmpty() || mergeScenario.getBase().exists()) : "Base artifact does not exist: " + mergeScenario.getBase(); } LOG.fine(() -> "Applying: " + this); if (target != null) { assert (target.exists()) : this + ": target " + target.getId() + " does not exist."; } // FIXME: I think this could be done easier. It's just too fucking ugly. T artifact = mergeScenario.get(0); artifact.merge(this, context); if (context.hasStatistics()) { Statistics statistics = context.getStatistics(); MergeScenarioStatistics mScenarioStatistics = statistics.getCurrentFileMergeScenarioStatistics(); boolean files = mergeScenario.getArtifacts().entrySet().stream() .map(Map.Entry::getValue) .map(T::getType) .allMatch(t -> t == FILE || t == DIRECTORY); if (files) { artifact.mergeOpStatistics(mScenarioStatistics, context); } else { mergeScenario.getArtifacts().entrySet().stream() .map(Map.Entry::getValue) .filter(a -> !BASE.equals(a.getRevision())) .forEach(a -> a.mergeOpStatistics(mScenarioStatistics, context)); } } } /** * Returns the <code>MergeScenario</code> containing the <code>Artifact</code>s this <code>MergeOperation</code> * is merging. * * @return the <code>MergeScenario</code> */ public MergeScenario<T> getMergeScenario() { return mergeScenario; } @Override public String getName() { return "MERGE"; } /** * Returns the target @code{Artifact}. * * @return the target */ public T getTarget() { return target; } @Override public String toString() { String dst = target == null ? "" : target.getId(); String mScenarioString = mergeScenario.toString(true); MergeType mergeType = mergeScenario.getMergeType(); return String.format("%s: %s %s %s INTO %s", getId(), getName(), mergeType, mScenarioString, dst); } }