/* Copyright 2014 MITRE Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mitre.provenance.dag; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.mitre.provenance.PLUSException; import org.mitre.provenance.plusobject.PLUSEdge; import org.mitre.provenance.plusobject.PLUSObject; /** * A DAGPath is a specific thread running through an existing LineageDAG. Because LineageDAG encapsulates all * of the complexity of edge voting and surrogates, DAGPaths can't exist independent of a particular DAG. * @author moxious */ public class DAGPath implements Cloneable { protected List <PathStep> steps; protected LineageDAG sourceDAG; protected PLUSObject head; protected PLUSObject tail; boolean directed = true; public DAGPath(LineageDAG dag, PLUSObject from, PLUSObject to, Collection<PathStep>steps) { this.sourceDAG = dag; this.head = from; this.tail = to; this.steps = new ArrayList<PathStep>(steps); if(steps == null) this.steps = new ArrayList<PathStep>(); directed = false; } // End DAGPath /** * Find a new DAGPath from one point to another within a particular dag. This will use depth-first search to locate the * path for you. No guarantee that this is the shortest or best path in the DAG. * @param dag the source DAG to use for finding the path. * @param from the starting point. * @param to the ending point * @throws PLUSException if the path does not exist in the DAG */ public DAGPath(LineageDAG dag, PLUSObject from, PLUSObject to) throws PLUSException { this.sourceDAG = dag; this.head = from; this.tail = to; steps = findPath(head, tail); } // End DAGPath /** Return true if the path contains a given edge, false otherwise */ public boolean contains(PLUSEdge edge) { String f = edge.getFrom().getId(); String t = edge.getTo().getId(); for(PathStep ps : steps) { if(ps.getOutboundEdge().getFrom().equals(f) && ps.getOutboundEdge().getTo().equals(t)) return true; } return false; } public List<PathStep> getSteps() { return steps; } /** Return the item at the head of the path */ public PLUSObject getHead() { return head; } /** Return the item at the tail of the path */ public PLUSObject getTail() { return tail; } public DAGPath clone() { ArrayList<PathStep>ns = new ArrayList<PathStep>(); for(PathStep ps : steps) { ns.add(ps.clone()); } return new DAGPath(sourceDAG, head, tail, ns); } /** * Add a path step to the path. This method checks for coherence - the step must be contiguous with the last step, otherwise * an exception will result. * @param step a new step to add to the chain. */ public void addStep(PathStep step) { if(steps.size() > 0) assert(step.getNode().getId().equals(steps.get(steps.size()-1).getOutboundEdge().getTo())); else assert step.getNode().getId().equals(head.getId()); steps.add(step); tail = sourceDAG.getNode(step.getOutboundEdge().getTo().getId()); } // End addStep public boolean exists() { return steps != null && steps.size() > 0; } public int getLength() { if (steps == null) return 0; return steps.size(); } /** * Does deep comparison of a path to make sure that one path is equal to another. */ public boolean equals(Object o) { if(!(o instanceof DAGPath)) return false; DAGPath other = (DAGPath)o; try { if(!sourceDAG.getId().equals(other.sourceDAG.getId())) return false; if(!head.getId().equals(other.head.getId())) return false; if(!tail.getId().equals(other.tail.getId())) return false; if(steps.size() != other.steps.size()) return false; for(int x=0; x<steps.size(); x++) { PathStep ps = steps.get(x); PathStep os = other.steps.get(x); if(!ps.node.getId().equals(os.node.getId())) return false; if(!ps.outboundEdge.getTo().equals(os.outboundEdge.getTo())) return false; } } catch(NullPointerException exc) { exc.printStackTrace(); return false; } return true; } public boolean contains(String oid) { for(int x=0; x<steps.size(); x++) { if(steps.get(x).getNode().getId().equals(oid)) return true; } return false; } // End contains /** * Determine whether the DAGPath is directed. Directed paths will have a sequence of steps that respect edge ordering, * and the source will be strictly before the target in the graph. * @return true if the path is directed, false otherwise. */ public boolean isDirected() { return directed; } public PathStep getLastStep() throws PLUSException { return getStep(steps.size()-1); } public PathStep getStep(int idx) throws PLUSException { if(idx < 0 || idx >= getLength()) throw new PLUSException("Step index number out of range: " + idx); return steps.get(idx); } public boolean contains(PLUSObject obj) { return contains(obj.getId()); } private List <PathStep> findPath(PLUSObject from, PLUSObject to) throws PLUSException { if(sourceDAG.getNode(from.getId()) == null) throw new PLUSException("DAGPath: Node " + from.getName() + " missing from DAG"); if(sourceDAG.getNode(to.getId()) == null) throw new PLUSException("DAGPath: Node " + to.getName() + " missing from DAG"); List <PathStep> result = new ArrayList <PathStep> (); List <PLUSEdge> edges = sourceDAG.getOutboundEdgesByNode(from.getId()); for(PLUSEdge e : edges) { if(e.getTo().equals(to.getId())) { result.add(new PathStep(from, e)); result.add(new PathStep(to, null)); return result; } // End if } // End for for(PLUSEdge e : edges) { PLUSObject nextNode = sourceDAG.getNode(e.getTo().getId()); if(nextNode == null) continue; List <PathStep> recursed = findPath(nextNode, to); if(recursed == null || recursed.size() == 0) continue; else { for(PathStep ps : recursed) result.add(ps); return result; } } return null; } // End findPath public String toString() { StringBuffer buf = new StringBuffer(""); if(!exists()) return "(No such path)"; for(PathStep ps : steps) { buf.append(ps.getNode().getName() + " => "); } // End for buf.append(tail.getName()); buf.append(" Length " + steps.size()); return buf.toString(); } // End toString() /** * Determine whether or not a path exists between two nodes in a LineageDAG * @param dag the dag to check * @param from the starting node * @param to the ending node * @return true if a path exists from the starting node to the ending node under the specified DAG. False otherwise. */ public static boolean pathExists(LineageDAG dag, PLUSObject from, PLUSObject to) { try { DAGPath path = new DAGPath(dag, from, to); return path.getLength() > 0; } catch(Exception e) { e.printStackTrace(); } return false; } // End pathExists } // End