au.edu.uq.nmerge.graph.VariantGraphArc.java Source code

Java tutorial

Introduction

Here is the source code for au.edu.uq.nmerge.graph.VariantGraphArc.java

Source

/*
 * NMerge is Copyright 2009-2011 Desmond Schmidt
 *
 * This file is part of NMerge. NMerge is a Java library for merging
 * multiple versions into multi-version documents (MVDs), and for
 * reading, searching and comparing them.
 *
 * NMerge is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package au.edu.uq.nmerge.graph;

import au.edu.uq.nmerge.Errors;
import au.edu.uq.nmerge.exception.MVDException;
import au.edu.uq.nmerge.mvd.Match;
import au.edu.uq.nmerge.mvd.Witness;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import java.util.*;

/**
 * An Arc is a fragment of data, a set of versions in a variant graph
 *
 * @author Desmond Schmidt
 */
public class VariantGraphArc<T> {
    /**
     * the set of versions
     */
    public Set<Witness> versions;
    /**
     * the origin Node
     */
    VariantGraphNode<T> from;
    /**
     * the destination Node
     */
    public VariantGraphNode<T> to;
    /**
     * the data of this Arc
     */
    List<T> data;
    /**
     * usually empty list of transpose children
     */
    LinkedList<VariantGraphArc<T>> children;
    /**
     * parent - can't be set as well as children
     */
    VariantGraphArc<T> parent;
    /**
     * parent id if subject of a transposition
     */
    int id;
    /**
     * id counter: used for parent/child relationships
     */
    static int arcId = 1;

    /**
     * Create a child arc
     *
     * @param versions the set of versions for the child
     * @param parent   the parent arc whose data will be copied
     */
    public VariantGraphArc(Set<Witness> versions, VariantGraphArc<T> parent) {
        this.versions = versions;
        // may attempt to create child of a child
        if (parent != null) {
            VariantGraphArc<T> temp = parent;
            while (temp.parent != null) {
                temp = temp.parent;
            }
            if (temp != parent) {
                parent = temp;
            }
            this.parent = temp;
            parent.addChild(this);
        }
        //checkForData("m_s id=\"page");
    }

    /**
     * Get an error over the children. Shouldn't be called if no
     * children, hence we throw an exception
     *
     * @return a ListIterator over the arc's children
     * @throws MVDException
     */
    ListIterator<VariantGraphArc<T>> childIterator() throws MVDException {
        if (children == null) {
            throw new MVDException("no children to arc");
        }
        return children.listIterator(0);
    }

    /**
     * Create a vanilla Arc
     *
     * @param versions the versions it belongs to
     * @param data     its data content
     */
    public VariantGraphArc(Set<Witness> versions, List<T> data) {
        this.versions = versions;
        this.data = data;
        //checkForData("m_s id=\"page");
    }

    /**
     * Set the to node
     *
     * @param to the new to node
     */
    public void setTo(VariantGraphNode<T> to) {
        this.to = to;
    }

    /**
     * Set the from node
     *
     * @param from the new from node
     */
    public void setFrom(VariantGraphNode<T> from) {
        this.from = from;
    }

    /**
     * Get the data of this arc
     *
     * @return the data as a byte array
     */
    public List<T> getData() {
        if (parent != null) {
            return parent.data;
        } else {
            return data;
        }
    }

    /**
     * Get the length of the data of this arc
     *
     * @return the length as an int
     */
    public int dataLen() {
        if (parent != null) {
            return parent.data.size();
        } else {
            return data.size();
        }
    }

    /**
     * Add one version to the arc
     *
     * @param version the version to add
     */
    public void addVersion(Witness version) {
        versions.add(version);
        if (to != null) {
            to.addIncomingVersion(version);
        }
        if (from != null) {
            from.addOutgoingVersion(version);
        }
    }

    /**
     * Add a child to the parent
     *
     * @param child the child arc
     */
    public void addChild(VariantGraphArc<T> child) {
        if (children == null) {
            children = new LinkedList<VariantGraphArc<T>>();
            id = VariantGraphArc.arcId++;
        }
        children.add(child);
        child.parent = this;
    }

    /**
     * Convert an Arc to a String for printing out
     *
     * @return the arc as a string
     */
    public String toString() {
        try {
            StringBuffer sb = new StringBuffer();
            if (from == null) {
                sb.append("(0)");
            } else if (from.isIncomingEmpty()) {
                sb.append("(s)");
            } else {
                sb.append("(" + from.nodeId + ")");
            }
            sb.append(Iterables.toString(versions));
            sb.append(": ");
            if (parent != null) {
                sb.append("[" + parent.id + ":");
            } else if (children != null) {
                sb.append("{" + id + ":");
            }
            sb.append(Iterables.toString(getData()));
            if (parent != null) {
                sb.append("]");
            } else if (children != null) {
                sb.append("}");
            }
            if (to == null) {
                sb.append("(0)");
            } else if (to.isOutgoingEmpty()) {
                sb.append("(e)");
            } else {
                sb.append("(" + to.nodeId + ")");
            }
            return sb.toString();
        } catch (Exception e) {
            Errors.LOG.error(e.getMessage(), e);
            return "";
        }
    }

    /**
     * Split an arc into two. This can get complex because arcs may be children
     * of a transposed parent or the parent itself or just a plain vanilla arc.
     *
     * @param offset the offset into data pointing to the first byte of the rhs
     * @return an array of split arcs (or maybe just one)
     */
    VariantGraphArc<T>[] split(int offset) throws MVDException {
        VariantGraphArc<T>[] arcs = null;
        // handle simple cases first
        if (offset == 0 || offset == dataLen()) {
            arcs = new VariantGraphArc[1];
            arcs[0] = this;
        } else if (parent == null) {
            if (children == null) {
                arcs = splitDataArc(offset);
            } else {
                arcs = splitParent(offset, null);
            }
        } else {
            arcs = splitChild(offset);
        }
        return arcs;
    }

    /**
     * Tell our parent to split, and that will split us
     *
     * @param offset the offset at which to split
     * @return the split child arcs
     */
    private VariantGraphArc<T>[] splitChild(int offset) throws MVDException {
        VariantGraphArc<T> splitParent = parent;
        while (splitParent.parent != null) {
            splitParent = parent;
        }
        return splitParent.splitParent(offset, this);
    }

    /**
     * Split us, the parent, and adjust our children
     *
     * @param offset  the offset at which to split
     * @param desired if not null return the split arcs of this
     *                child, not of the parent
     * @return the split parent or child arc
     */
    private VariantGraphArc<T>[] splitParent(int offset, VariantGraphArc<T> desired) throws MVDException {
        VariantGraphArc<T>[] arcs = splitDataArc(offset);
        for (int i = 0; i < children.size(); i++) {
            VariantGraphArc<T> child = children.get(i);
            VariantGraphArc<T> b = new VariantGraphArc<T>(Sets.newHashSet(child.versions), arcs[0]);
            VariantGraphArc<T> c = new VariantGraphArc<T>(Sets.newHashSet(child.versions), arcs[1]);
            VariantGraphNode<T> childFrom = child.from;
            VariantGraphNode<T> childTo = child.to;
            childFrom.removeOutgoing(child);
            childTo.removeIncoming(child);
            childFrom.addOutgoing(b);
            childTo.addIncoming(c);
            VariantGraphNode<T> n = new VariantGraphNode<T>();
            n.addIncoming(b);
            n.addOutgoing(c);
            if (child == desired) {
                arcs = new VariantGraphArc[2];
                arcs[0] = b;
                arcs[1] = c;
            }
        }
        return arcs;
    }

    /**
     * This is called whenever we have to physically split an arc
     * that is not a child and has its own data
     *
     * @param offset point before which to split
     * @return an array of two split arcs
     */
    private VariantGraphArc<T>[] splitDataArc(int offset) throws MVDException {
        VariantGraphArc<T>[] arcs = new VariantGraphArc[2];
        arcs[0] = new VariantGraphArc<T>(Sets.newHashSet(versions), Lists.newArrayList(data.subList(0, offset)));
        arcs[1] = new VariantGraphArc<T>(Sets.newHashSet(versions),
                Lists.newArrayList(data.subList(offset, dataLen())));
        installSplit(arcs);
        return arcs;
    }

    /**
     * Replace this arc in the graph with two split ones
     *
     * @param arcs two arcs to replace this one
     */
    private void installSplit(VariantGraphArc<T>[] arcs) throws MVDException {
        // now replace the existing arc with the two split ones
        from.replaceOutgoing(this, arcs[0]);
        VariantGraphNode<T> inter = new VariantGraphNode<T>();
        inter.addIncoming(arcs[0]);
        inter.addOutgoing(arcs[1]);
        to.replaceIncoming(this, arcs[1]);
    }

    /**
     * Required for membership tests in hashmaps and treemaps
     */
    public boolean equals(Object other) {
        VariantGraphArc<?> otherArc = (VariantGraphArc<?>) other;
        return versions.equals(otherArc.versions) && dataEquals(otherArc) && from == otherArc.from
                && to == otherArc.to;
    }

    /**
     * Is the data of the two arcs equal?
     *
     * @param otherArc the other arc to compare
     * @return true if they two arcs have same data
     */
    private boolean dataEquals(VariantGraphArc<?> otherArc) {
        List<T> data1 = getData();
        List<?> data2 = otherArc.getData();
        if ((data1 == null && data2 != null) || (data1 != null && data2 == null)) {
            return false;
        } else if (data1 == null && data2 == null) {
            return true;
        } else {
            return data1.equals(data2);
        }
    }

    /**
     * Is this arc a hint?
     *
     * @return true if it is a hint
     */
    boolean isHint() {
        return versions.isEmpty();
    }

    /**
     * Convert and Arc to its pair form
     *
     * @param parents map of available parents
     * @param orphans map of available orphans
     * @return the pair
     */
    Match<T> toPair(HashMap<VariantGraphArc<T>, Match<T>> parents, HashMap<VariantGraphArc<T>, Match<T>> orphans)
            throws MVDException {
        if (isHint()) {
            throw new MVDException("Ooops! hint detected!");
        }
        Match<T> p = new Match<T>(versions, data);
        if (this.parent != null) {
            // we're a child - find our parent
            Match<T> q = parents.get(parent);
            if (q != null) {
                q.addChild(p);
                // if this is the last child of the parent remove it
                if (parent.numChildren() == q.numChildren()) {
                    parents.remove(parent);
                }
            } else // we're orphaned for now
            {
                orphans.put(this, p);
            }
        } else if (children != null) {
            // we're a parent
            for (int i = 0; i < children.size(); i++) {
                VariantGraphArc<T> child = children.get(i);
                Match<T> r = orphans.get(child);
                if (r != null) {
                    p.addChild(r);
                    orphans.remove(child);
                }
            }
            if (p.numChildren() < this.numChildren()) {
                parents.put(this, p);
            }
        }
        return p;
    }

    /**
     * Does the arc have no data?
     *
     * @return true if so
     */
    public boolean isEmpty() {
        return data.isEmpty();
    }

    /**
     * Is this arc not attached to any node at the end?
     *
     * @return true if this is so
     */
    boolean unattached() {
        return to == null;
    }
    // extra routines for nmerge

    /**
     * Get the from node
     *
     * @return a Node possibly null
     */
    public VariantGraphNode<T> getFrom() {
        return from;
    }

    /**
     * Get the to node
     *
     * @return a Node possibly null
     */
    public VariantGraphNode<T> getTo() {
        return to;
    }

    /**
     * Remove a child from the parent
     *
     * @param child the child arc
     */
    public void removeChild(VariantGraphArc<T> child) {
        assert children.contains(child) : "removeChild: child " + child + " not found!";
        children.remove(child);
        if (children.size() == 0) {
            children = null;
        }
    }

    /**
     * How many children do we have?
     *
     * @return the number of children in the child list
     */
    int numChildren() {
        return (children == null) ? 0 : children.size();
    }

    /**
     * Is this arc a parent?
     *
     * @return true if it has children
     */
    boolean isParent() {
        return children != null && children.size() > 0;
    }

    /**
     * Is this arc a child?
     *
     * @return true if it has a parent
     */
    boolean isChild() {
        return parent != null;
    }

    /**
     * We are dying. Pass our data on to our children. This
     * happens if we are being deleted.
     */
    public void passOnData() {
        ListIterator<VariantGraphArc<T>> iter = children.listIterator();
        VariantGraphArc<T> newParent = iter.next();
        newParent.data = this.data;
        newParent.setParent(null);
        while (iter.hasNext()) {
            VariantGraphArc<T> a = iter.next();
            newParent.addChild(a);
        }
    }

    /**
     * Set the new parent in case of adoption
     *
     * @param parent the new parent
     */
    void setParent(VariantGraphArc<T> parent) {
        this.parent = parent;
    }

    /**
     * Verify that our internal data is up to snuff
     *
     * @throws MVDException throw an exception if not
     */
    void verify() throws MVDException {
        if (data == null && parent == null) {
            throw new MVDException("Arc data is null and shouldn't be");
        }
    }

    /**
     * Do we have a child in the given version?
     *
     * @param version the version to test for
     * @return true if we have children and one of them has the version
     */
    boolean hasChildInVersion(Witness version) {
        if (children != null) {
            ListIterator<VariantGraphArc<T>> iter = children.listIterator(0);
            while (iter.hasNext()) {
                VariantGraphArc<T> child = iter.next();
                if (child.versions.contains(version)) {
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }
}