Java tutorial
/** * Copyright (C) 2013-2014 Olaf Lessenich * Copyright (C) 2014-2017 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.artifact; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import de.fosd.jdime.config.merge.MergeContext; import de.fosd.jdime.config.merge.Revision; import de.fosd.jdime.matcher.matching.Matching; import de.fosd.jdime.operations.MergeOperation; import de.fosd.jdime.stats.StatisticsInterface; import de.fosd.jdime.strdump.DumpMode; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; /** * A generic <code>Artifact</code> that has a tree structure. * * @author Olaf Lessenich * * @param <T> * type of artifact */ public abstract class Artifact<T extends Artifact<T>> implements Comparable<T>, StatisticsInterface { private static final Logger LOG = Logger.getLogger(Artifact.class.getCanonicalName()); /** * Children of the artifact. */ private List<T> children; /** * Left side of a conflict. */ protected T left; /** * Right side of a conflict. */ protected T right; /** * Whether this artifact represents a conflict. */ private boolean conflict; /** * Whether this artifact represents a choice node. */ private boolean choice; /** * If the artifact is a choice node, it has variants (values of map) that are present under conditions (keys of map) */ protected HashMap<String, T> variants; /** * Map to store matches. */ protected Map<Revision, Matching<T>> matches; /** * Whether the artifact has been already merged. */ private boolean merged; /** * Parent artifact. */ private T parent; /** * Revision the artifact belongs to. */ private Revision revision; /** * Number used to identify the artifact. */ private int number; private boolean hashValid; private String hash; /** * Constructs a new <code>Artifact</code>. * * @param rev * the <code>Revision</code> for the <code>Artifact</code> * @param number * the DFS index of the <code>Artifact</code> in the <code>Artifact</code> tree it is a part of */ protected Artifact(Revision rev, int number) { this.children = new ArtifactList<>(); this.matches = new HashMap<>(); this.revision = rev; this.number = number; this.hashValid = false; this.hash = null; } /** * Copies the given {@link Artifact} detached from its tree. * * @param toCopy * the {@link Artifact} to copy * @see #copy() */ protected Artifact(Artifact<T> toCopy) { this.children = new ArtifactList<>(); this.left = toCopy.left != null ? Artifacts.copyTree(toCopy.left) : null; this.right = toCopy.right != null ? Artifacts.copyTree(toCopy.right) : null; if (toCopy.variants != null) { this.variants = new HashMap<>(); toCopy.variants.entrySet().forEach(en -> variants.put(en.getKey(), Artifacts.copyTree(en.getValue()))); } copyMatches(toCopy); this.conflict = toCopy.conflict; this.choice = toCopy.choice; this.merged = toCopy.merged; this.revision = toCopy.revision; this.number = toCopy.number; } /** * Must be implemented as: * <p> * {@code * protected T self() { * return this; * } * } * <p> * This method solves some issues caused by the recursive generic type signature of the {@link Artifact} class. * * @return {@code this} */ protected abstract T self(); /** * Adds a matching. * * @param matching * matching to be added */ public void addMatching(Matching<T> matching) { matches.put(matching.getMatchingArtifact(this).getRevision(), matching); } /** * Copies this {@link Artifact} detached from its tree (meaning {@link #parent} will be {@code null} and * {@link Artifact#children} will be empty. Subclasses must implement this method by using their own private * copy constructor that calls the protected copy constructor of the {@link Artifact} base class. * * @return a copy of this {@link Artifact} * @see Artifacts#copyTree(Artifact) */ public abstract T copy(); /** * Copies the {@link Artifact#matches} of {@code toCopy}, replaces {@code toCopy} with {@code this} in them and * adds them to {@code this} {@link Artifact}. * * @param toCopy * the {@link Artifact} to copy the {@link Artifact#matches} from */ public void copyMatches(Artifact<T> toCopy) { if (toCopy.matches == null) { return; } this.matches = new HashMap<>(); toCopy.matches.entrySet().forEach(en -> { Matching<T> clone = en.getValue().clone(); clone.updateMatching(self(), toCopy.self()); matches.put(en.getKey(), clone); }); } /** * Clears all matches recursively. */ public void clearAllMatches() { matches = new HashMap<>(); for (T child : getChildren()) { child.clearAllMatches(); } } /** * Returns an <code>Artifact</code> that represents a merge conflict. * A conflict contains two alternative <code>Artifact</code> (left and right) and is handled in a special way * while pretty-printed. * * @param left * left alternative <code>Artifact</code> * @param right * right alternative <code>Artifact</code> * @return conflict <code>Artifact</code> */ public abstract T createConflictArtifact(T left, T right); /** * Returns a choice artifact. * * @param condition presence condition * @param artifact conditional artifact * @return choice artifact */ public abstract T createChoiceArtifact(String condition, T artifact); /** * Returns an empty <code>Artifact</code>. This is used while performing two-way merges where the * base <code>Artifact</code> is empty. * * @param revision * the <code>Revision</code> for the artifact * @return an empty artifact */ public abstract T createEmptyArtifact(Revision revision); /** * Pretty-prints the <code>Artifact</code> to source code. * * @return Pretty-printed AST (source code) */ public abstract String prettyPrint(); /** * Dumps this <code>Artifact</code> to a <code>String</code> using the given <code>DumpMode</code>. Uses the * {@link Artifact#toString()} method for producing labels for nodes. * * @param mode * the <code>DumpMode</code> to use * @return the dump result */ public String dump(DumpMode mode) { return dump(mode, Artifact::toString); } /** * Dumps this <code>Artifact</code> to a <code>String</code> using the given <code>DumpMode</code>. * * @param mode * the <code>DumpMode</code> to use * @param getLabel * the <code>Function</code> for producing labels for nodes * @return the dump result */ public String dump(DumpMode mode, Function<Artifact<T>, String> getLabel) { return mode.getDumper().dump(this, getLabel); } /** * Returns true if this artifact physically exists. * * @return true if the artifact exists. */ public abstract boolean exists(); /** * Adds the given {@link Artifact} to the children of this {@link Artifact} and sets the {@link #parent} * accordingly. * * @param child * the {@link Artifact} to add as a child */ public void addChild(T child) { if (canAddChild(child)) { children.add(child); child.setParent(self()); invalidateHash(); } } /** * Replaces the child at the specified position in the list of children of this {@link Artifact} with the specified * child. * * @param child * the child to be added at the specified position * @param index * the index of the child to replace * @see List#set(int, Object) */ public void setChild(T child, int index) { if (canAddChild(child)) { children.set(index, child); child.setParent(self()); invalidateHash(); } } /** * Determines whether the given {@link Artifact} {@code toAdd} may be added to the children of this * {@link Artifact}. Any child passed to {@link #addChild(Artifact)} will not be added if this method returns * {@code false} for it. Subclasses overriding this method should log the reason for returning {@code false} if they * do so. The default implementation returns {@code true}. * * @param toAdd * the {@link Artifact} to add * @return whether the {@code child} may be added */ protected boolean canAddChild(T toAdd) { return true; } /** * Removes all children of this {@link Artifact}. */ public void clearChildren() { if (hasChildren()) { children.clear(); invalidateHash(); } } /** * Returns the index of the first occurrence of the specified child in the list of children, or -1 if the given * {@link Artifact} is not a child of this {@link Artifact}. * * @param child * the child whose index is to be returned * @return the index of the child or -1 * @see List#indexOf(Object) */ public int indexOf(T child) { return children.indexOf(child); } /** * Return child <code>Artifact</code> at position i. * * @param i * position of child <code>Artifact</code> * @return child <code>Artifact</code> at position i */ public T getChild(int i) { return children.get(i); } /** * Returns an unmodifiable view of the children of this {@code Artifact}. * * @return an unmodifiable view of the children of this {@code Artifact} * @see Collections#unmodifiableList(List) */ public List<T> getChildren() { return Collections.unmodifiableList(children); } /** * Sets the children of the <code>Artifact</code>. * * @param children * the new children to set * @throws NullPointerException * if {@code children} is {@code null} */ public void setChildren(List<T> children) { Objects.requireNonNull(children, "The list of children must not be null."); this.children = children; invalidateHash(); } /** * Applies the given {@code action} to the children of this {@link Artifact} and invalidates the tree hash as * necessary. * * @param action * the action to apply to the list of {@link #children} */ protected void modifyChildren(Consumer<List<T>> action) { int hashBefore = children.hashCode(); action.accept(children); if (children.hashCode() != hashBefore) { invalidateHash(); } } /** * Returns the number of children the <code>Artifact</code> has. * * @return number of children */ public int getNumChildren() { return children.size(); } /** * Returns true if the <code>Artifact</code> has children. * * @return true if the <code>Artifact</code> has children */ public boolean hasChildren() { return !children.isEmpty(); } /** * Returns the identifier of the <code>Artifact</code>, * which contains the <code>Revision</code> name and a number. * * This method is basically useful for debugging JDime. * * @return identifier of the <code>Artifact</code> */ public abstract String getId(); /** * Returns a hash of the tree rooted in this {@code Artifact}. * * @return the tree hash */ public String getTreeHash() { if (hashValid) { return hash; } MessageDigest digest = DigestUtils.getSha256Digest(); DigestUtils.updateDigest(digest, hashId()); if (hasChildren()) { children.forEach(c -> DigestUtils.updateDigest(digest, c.getTreeHash())); hash = "1" + Hex.encodeHexString(digest.digest()); } else { hash = "0" + Hex.encodeHexString(digest.digest()); } hashValid = true; return hash; } /** * Returns the {@code String} identifying this {@code Artifact} for the purposes of calculating the tree hash in * {@link #getTreeHash()}; * * @return the identifying {@code String} to be hashed */ protected abstract String hashId(); /** * Invalidates the hashes of this {@code Artifact} and all its parents. */ protected void invalidateHash() { hashValid = false; hash = null; if (parent != null) { parent.invalidateHash(); } } /** * Returns the <code>Matching</code> for a specific <code>Revision</code> or <code>null</code> if there is no such * <code>Matching</code>. * * @param rev * <code>Revision</code> * @return <code>Matching</code> with <code>Revision</code> */ public Matching<T> getMatching(Revision rev) { return matches.get(rev); } /** * Returns all <code>Matching</code>s added for this <code>Artifact</code>. * * @return the <code>Matching</code>s */ public Set<Matching<T>> getMatchings() { return new HashSet<>(matches.values()); } /** * Returns an unmodifiable view of the map used to store the <code>Matchings</code> of this <code>Artifact</code>. * * @return the matchings */ public Map<Revision, Matching<T>> getMatches() { return Collections.unmodifiableMap(matches); } /** * Returns the number of the <code>Artifact</code>. * * @return number of the <code>Artifact</code> */ public int getNumber() { return number; } /** * Sets the number of all <code>Artifact</code>s contained in the tree rooted at this artifact to their index in * a depth-first traversal of the tree. */ public void renumber() { renumber(new AtomicInteger()::getAndIncrement); } /** * Sets the number of all <code>Artifact</code>s contained in the tree rooted at this artifact to the number * supplied by <code>number</code> when traversing the tree in depth-first order. * * @param number * the supplier for the new numbers */ private void renumber(Supplier<Integer> number) { this.number = number.get(); for (Artifact<T> child : children) { child.renumber(number); } } /** * Returns the parent <code>Artifact</code>. * * @return the parent <code>Artifact</code> */ public T getParent() { return parent; } /** * Returns the <code>Revision</code> the <code>Artifact</code> belongs to. * * @return the <code>Revision</code> the <code>Artifact</code> belongs to. */ public Revision getRevision() { return revision; } /** * Returns the maximum depth of any node in the tree. * * @return the maximum depth */ public int getMaxDepth() { return 1 + children.parallelStream().map(T::getMaxDepth).max(Integer::compare).orElse(0); } /** * Returns the size of the subtree. The <code>Artifact</code> itself is not included. * * @return size of subtree */ public int getSubtreeSize() { int size = getNumChildren(); for (int i = 0; i < getNumChildren(); i++) { size += getChild(i).getSubtreeSize(); } return size; } /** * Returns the size of the tree. The <code>Artifact</code> itself is also included. * * @return size of tree */ public int getTreeSize() { return getSubtreeSize() + 1; } /** * Returns whether the subtree rooted in this {@link Artifact} has changes compared to the given {@link Revision}. * Returns {@code false} if {@code revision} is the {@link Revision} of this {@link Artifact}. * * @param revision the opposite {@link Revision} * @return true iff any {@link Artifact} in the tree under this {@link Artifact} represents a changed compared to * the given {@link Revision} */ public boolean hasChanges(Revision revision) { if (this.revision.equals(revision)) { return false; } if (!hasMatching(revision)) { return true; } T match = getMatching(revision).getMatchingArtifact(this); return getTreeSize() != match.getTreeSize() || !getTreeHash().equals(match.getTreeHash()); } /** * Returns whether this <code>Artifact</code> has any matches. * * @return true if the <code>Artifact</code> has matches */ public boolean hasMatches() { return !matches.isEmpty(); } /** * Returns whether this {@link Artifact} has been matched with an {@link Artifact} from the given {@link Revision}. * * @param rev * the opposite {@link Revision} * @return true iff there is a match from the given {@code revision} */ public boolean hasMatching(Revision rev) { logMatchings(rev); if (isChoice()) { return variants.entrySet().stream().map(Entry::getValue).anyMatch(var -> var.hasMatching(rev)); } else { return matches.containsKey(rev); } } /** * Returns whether this {@link Artifact} has been matched with the given {@link Artifact} {@code other}. * * @param other * the opposite {@link Artifact} * @return true iff this {@link Artifact} has been matched with the given {@link Artifact} {@code other} */ public boolean hasMatching(T other) { Revision otherRev = other.getRevision(); logMatchings(otherRev); if (isChoice()) { return variants.entrySet().stream().map(Entry::getValue).anyMatch(var -> var.hasMatching(other)); } else { return matches.containsKey(otherRev) && matches.get(otherRev).getMatchingArtifact(this) == other; } } /** * Logs (if FINEST is enabled) what matchings exist for this {@link Artifact} in the given {@link Revision}. * * @param rev * the opposite {@link Revision} */ private void logMatchings(Revision rev) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Checking for matchings for " + getId() + " in revision " + rev + "."); if (matches.isEmpty()) { LOG.finest("No matchings for " + getId() + " in revision " + rev + "."); } else { for (Entry<Revision, Matching<T>> entry : matches.entrySet()) { Revision otherRev = entry.getKey(); T matchedArtifact = entry.getValue().getMatchingArtifact(this); LOG.finest("Matching found for revision " + otherRev + " is " + matchedArtifact.getId()); } } } } /** * Returns a <code>Supplier</code> producing a unique label for this <code>Artifact</code> or an empty optional * if there is no such label. If there is a unique label a more efficient <code>UnorderedMatcher</code> can be used. * * @return optionally a <code>Supplier</code> producing a unique label */ public abstract Optional<Supplier<String>> getUniqueLabel(); /** * Returns true if the <code>Artifact</code> is a conflict node. * * @return true if the <code>Artifact</code> represents a conflict */ public boolean isConflict() { return conflict; } /** * Returns true if the artifact is a choice node. * * @return true if the artifact represents a choice node */ public final boolean isChoice() { return choice; } /** * Returns whether this {@link Artifact} is a virtual one. * * @return true iff the {@link Artifact} is virtual */ public final boolean isVirtual() { return isConflict() || isChoice(); } /** * Returns true if the <code>Artifact</code> is empty. * * @return true if the <code>Artifact</code> is empty */ public abstract boolean isEmpty(); /** * Returns true if the <code>Artifact</code> has already been merged. * @return true if the <code>Artifact</code> has already been merged */ public boolean isMerged() { return merged; } /** * Returns true if the declaration order of the <code>Artifact</code> is essential. * * @return true if the declaration order of the <code>Artifact</code> is essential */ public abstract boolean isOrdered(); /** * Returns true if the <code>Artifact</code> is the root node. * * @return true if the <code>Artifact</code> is the root node */ public boolean isRoot() { return getParent() == null; } /** * Returns true, if this <code>Artifact</code> matches another <code>Artifact</code>. * * @param other * other <code>Artifact</code> * @return true, if the <code>Artifact</code>s match */ public abstract boolean matches(T other); /** * Returns true, if this <code>Artifact</code> is assigned the same * category as the other <code>Artifact</code>. * * @param other * other <code>Artifact</code> * @return true, if the <code>Artifact</code>s' categories match */ public abstract boolean categoryMatches(T other); /** * Performs a merge on the provided merge triple. * This method selects the <code>MergeStrategy</code> and triggers the merge. * * @param operation * merge operation * @param context * merge context */ public abstract void merge(MergeOperation<T> operation, MergeContext context); /** * Marks this <code>Artifact</code> as a conflict. * * @param left * left alternative * @param right * right alternative */ protected void setConflict(T left, T right) { if (left != null) { left.clearAllMatches(); } if (right != null) { right.clearAllMatches(); } this.conflict = true; this.left = left; this.right = right; } /** * Returns the left alternative of a conflict. * * @return the left <code>Artifact</code> */ public T getLeft() { return left; } /** * Returns the right alternative of a conflict. * * @return the right <code>Artifact</code> */ public T getRight() { return right; } /** * Marks this artifact as a choice. * * @param condition presence condition * @param artifact conditional artifact */ public final void setChoice(final String condition, final T artifact) { this.choice = true; if (condition == null) { throw new RuntimeException("condition must not be null!"); } addVariant(condition, artifact); } public void addVariant(String condition, final T artifact) { if (!choice) { throw new RuntimeException("addVariant() can only be called on choice nodes!"); } if (condition == null) { throw new RuntimeException("condition must not be null!"); } LOG.fine("Add node " + artifact.getId() + " under condition " + condition); if (variants == null) { variants = new HashMap<>(); } // merge conditions for same artifact List<String> mergedConditions = new ArrayList<>(); for (String existingCondition : variants.keySet()) { if (variants.get(existingCondition).equals(artifact)) { mergedConditions.add(existingCondition); condition = existingCondition + " || " + condition; } } for (String mergedCondition : mergedConditions) { variants.remove(mergedCondition); } variants.put(condition, artifact); } /** * Set whether the <code>Artifact</code> has already been merged. */ public void setMerged() { this.merged = true; } /** * Sets the number of the <code>Artifact</code> * @param number * the number to set */ public void setNumber(int number) { this.number = number; } /** * Sets the parent <code>Artifact</code>. * * @param parent * the parent to set */ protected void setParent(T parent) { this.parent = parent; } /** * Sets the <code>Revision</code>. * * @param revision * the <code>Revision</code> to set */ public void setRevision(Revision revision) { setRevision(revision, false); } public void setRevision(Revision revision, boolean recursive) { this.revision = revision; if (recursive) { for (T child : children) { child.setRevision(revision, true); } } } @Override public abstract String toString(); @Override public final int compareTo(T o) { return getId().compareTo(o.getId()); } /** * If the artifact is a choice node, it has variants (values of map) that are present under conditions (keys of map) * * @return the variants represented by this choice {@link Artifact} */ public Map<String, T> getVariants() { return variants; } /** * Attempts to find an <code>Artifact</code> with the given number in the <code>Artifact</code> tree with this * <code>Artifact</code> at its root. * * @param number * the number of the <code>Artifact</code> to find * @return optionally the <code>Artifact</code> with the sought number */ public Optional<Artifact<T>> find(int number) { if (this.number == number) { return Optional.of(this); } return children.stream().map(c -> c.find(number)).filter(Optional::isPresent).findFirst() .map(Optional::get); } }