/**
* 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.config.merge;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
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;
/**
* A <code>MergeScenario</code> collects the <code>Artifact</code>s that are participating in the merge and stores its
* <code>MergeType</code>.
*
* @param <T>
* the type of the <code>Artifact</code>s being merged
* @author Olaf Lessenich
* @author Georg Seibt
*/
public class MergeScenario<T extends Artifact<T>> {
private static final Logger LOG = Logger.getLogger(MergeScenario.class.getCanonicalName());
public static final Revision LEFT = new Revision("left");
public static final Revision BASE = new Revision("base");
public static final Revision RIGHT = new Revision("right");
public static final Revision TARGET = new Revision("target");
public static final Revision MERGE = new Revision("merge");
public static final Revision CONFLICT = new Revision("conflict");
public static final Revision CHOICE = new Revision("choice");
private MergeType mergeType;
private Map<Revision, T> artifacts;
/**
* Constructs a {@link MergeType#TWOWAY} or {@link MergeType#THREEWAY} merge scenario.
*
* @param mergeType
* the <code>MergeType</code> for this <code>MergeScenario</code>
* @param left
* the <code>Artifact</code> for the {@link #LEFT} <code>Revision</code>
* @param base
* the <code>Artifact</code> for the {@link #BASE} <code>Revision</code>
* @param right
* the <code>Artifact</code> for the {@link #RIGHT} <code>Revision</code>
*/
public MergeScenario(MergeType mergeType, T left, T base, T right) {
if (mergeType != MergeType.TWOWAY && mergeType != MergeType.THREEWAY) {
LOG.warning(() -> String.format("Constructing a %s MergeScenario using the Left/Base/Right constructor.", mergeType));
}
this.artifacts = new LinkedHashMap<>();
this.mergeType = mergeType;
this.artifacts.put(left.getRevision(), left);
this.artifacts.put(base.getRevision(), base);
this.artifacts.put(right.getRevision(), right);
}
/**
* Constructs a {@link MergeType#NWAY} <code>MergeScenario</code> from the given <code>Artifact</code>s.
*
* @param inputArtifacts
* the <code>Artifact</code>s participating in the merge
*/
public MergeScenario(List<T> inputArtifacts) {
this.artifacts = new LinkedHashMap<>();
this.mergeType = MergeType.NWAY;
for (T artifact : inputArtifacts) {
artifacts.put(artifact.getRevision(), artifact);
}
}
/**
* Performs a shallow copy (the actual Artifacts that are part of the MergeScenario will not be copied).
*
* @param toCopy
* the <code>MergeScenario</code> to copy
*/
public MergeScenario(MergeScenario<T> toCopy) {
this.mergeType = toCopy.mergeType;
this.artifacts = new HashMap<>(toCopy.artifacts);
}
/**
* Returns the <code>MergeType</code> of this <code>MergeScenario</code>.
*
* @return the <code>MergeType</code>
*/
public MergeType getMergeType() {
return mergeType;
}
/**
* Returns an unmodifiable view of the <code>Map</code> used to store the <code>Artifact</code>s in this
* <code>MergeScenario</code>.
*
* @return the <code>Artifact</code>s in this <code>MergeScenario</code>
*/
public Map<Revision, T> getArtifacts() {
return Collections.unmodifiableMap(artifacts);
}
/**
* Returns the n-th <code>Artifact</code> that was added to this <code>MergeScenario</code>. Will return
* <code>null</code> if <code>n</code> is invalid (not smaller than the number of artifacts in the scenario).
*
* @param n the number of the artifact to return
* @return the n-th <code>Artifact</code> of this <code>MergeScenario</code> by insertion order
*/
public T get(int n) {
int i = 0;
T artifact = null;
for (Map.Entry<Revision, T> entry : artifacts.entrySet()) {
if (i == n) {
artifact = entry.getValue();
}
i++;
}
return artifact;
}
/**
* Returns the left <code>Artifact</code>.
*
* @return the left <code>Artifact</code>
*/
public T getLeft() {
return artifacts.get(LEFT);
}
/**
* Sets the left <code>Artifact</code> to the new value.
*
* @param left the new left <code>Artifact</code>
*/
public void setLeft(T left) {
artifacts.put(LEFT, left);
}
/**
* Returns the base <code>Artifact</code>.
*
* @return the base <code>Artifact</code>
*/
public T getBase() {
return artifacts.get(BASE);
}
/**
* Sets the base <code>Artifact</code> to the new value.
*
* @param base the new base <code>Artifact</code>
*/
public void setBase(T base) {
artifacts.put(BASE, base);
}
/**
* Returns the right <code>Artifact</code>.
*
* @return the right <code>Artifact</code>
*/
public T getRight() {
return artifacts.get(RIGHT);
}
/**
* Sets the right <code>Artifact</code> to the new value.
*
* @param right the new right <code>Artifact</code>
*/
public void setRight(T right) {
artifacts.put(RIGHT, right);
}
/**
* Returns whether this is a valid merge scenario.
*
* @return true iff the merge scenario is valid
*/
public boolean isValid() {
// TODO: this needs to be reimplemented considering the possibility of an n-way merge
return true;
}
/**
* Returns an <code>ArtifactList</code> containing the <code>Artifact</code>s in this <code>MergeScenario</code>
* in the order of their insertion.
*
* @return the <code>ArtifactList</code>
*/
public ArtifactList<T> asList() {
return artifacts.entrySet().stream().map(Map.Entry::getValue).collect(ArtifactList::new, ArrayList::add, ArrayList::addAll);
}
@Override
public String toString() {
return toString(" ", false);
}
/**
* Returns a <code>String</code> representing the <code>MergeScenario</code> separated by a whitespace.
*
* @param humanReadable
* whether to omit empty dummy <code>Artifact</code>s
* @return the <code>String</code> representing this <code>MergeScenario</code>
*/
public String toString(boolean humanReadable) {
return toString(" ", humanReadable);
}
/**
* Returns a <code>String</code> representing the <code>MergeScenario</code>.
*
* @param sep
* the separator to use between the representations of the <code>Artifact</code>s
* @param humanReadable
* whether to omit empty dummy <code>Artifact</code>s
* @return the <code>String</code> representing this <code>MergeScenario</code>
*/
public String toString(String sep, boolean humanReadable) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<Revision, T> entry : artifacts.entrySet()) {
T artifact = entry.getValue();
if (!humanReadable || !artifact.isEmpty()) {
sb.append(artifact.getId()).append(sep);
}
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MergeScenario<?> that = (MergeScenario<?>) o;
return Objects.equals(mergeType, that.mergeType) &&
Objects.equals(artifacts, that.artifacts);
}
@Override
public int hashCode() {
return Objects.hash(mergeType, artifacts);
}
}