/* * This file is part of Alida, a Java library for * Advanced Library for Integrated Development of Data Analysis Applications. * * Copyright (C) 2010 - @YEAR@ * * This program 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/>. * * Fore more information on Alida, visit * * http://www.informatik.uni-halle.de/alida/ * */ /* * Most recent change(s): * * $Rev$ * $Date$ * $Author$ * */ package de.unihalle.informatik.Alida.operator; import de.unihalle.informatik.Alida.exceptions.*; import de.unihalle.informatik.Alida.helpers.ALDFilePathManipulator; import de.unihalle.informatik.Alida.operator.ALDOperator.HidingMode; import java.util.*; import org.apache.xmlbeans.*; import org.graphdrawing.graphml.xmlns.*; /** This class handles the construction of a processing DAG from opNode instances and writing this processing history as extended graphml to file. */ public class ALDProcessingDAG { /** Type of the processing history to be generated. * COMPLETE creates a complete history for all opnodes, DATADEPENDENCIES * history according to data dependencies for all opnodes, * OPNODETYPE respects the preference of the operators. */ public enum HistoryType { COMPLETE, DATADEPENDENCIES, OPNODETYPE}; /** Type of the history to be generated. */ private HistoryType historyType = HistoryType.DATADEPENDENCIES; /** debug flag for debugging output. * We make this static to be able to control from outside as instances of * this class are created inside methods we use. */ private static boolean debug = false; /** Ignore hidden flag of opnodes when creating a processing history. * i.e. all opnodes are visible. */ private boolean ignoreHiding = false; /** Character separating graphId prefix from rest */ public static final char idSeparator = ':'; /** This index is the index used to build the prefix for all Ids for elements * for the next history to add to the current graphml. * The currently build graph has index 0. */ private int graphIndex; // these are the items we have found durung traceback and will e part of the // processing history, i.e. graphml history private Vector<ALDOpNode> allOpNodes; // opNodes found in this processing DAG private Vector<ALDEdge> allEdges; // edges in processing DAG private Vector<ALDDataPort> allDataports; // all data in processing DAG private Vector<ALDOpNode> opNodesClone; // opNodes clone to generate unique Ids private Vector<ALDOpNode> opNodesInGraph; // opNodes written to the graph used for checks in addEdge private Vector<ALDOpNodePort> opNodePortsTraced; // all OpNode ports already traced in processing DAG private Vector<ALDDataPort> writtenDataports; // data already written to the DAG /** Set the debug state * @param value New debug state */ public static void setDebug( boolean value) { debug = value; } /** Return the debug state * @return Current debug state */ public static boolean getDebug() { return debug; } /** Trace back the implicit DAG created during processing for sourceObjOfHistory. * As a side effect the vector allOpNodes, allEdges, allDataports are filled * containing all opnodes, dataports and edges as elements of the processing * history of sourceObjOfHistory. * * @param sourceObjOfHistory Object for which the processing history is to be created */ private void tracebackDAG( Object sourceObjOfHistory) throws ALDProcessingDAGException { graphIndex = 1; allOpNodes = new Vector<ALDOpNode>(); allEdges = new Vector<ALDEdge>(); opNodePortsTraced = new Vector<ALDOpNodePort>(); allDataports = new Vector<ALDDataPort>(); writtenDataports = new Vector<ALDDataPort>(); ALDPort port = ALDOperator.portHashAccess.getHistoryLink(sourceObjOfHistory); if ( debug ) { System.out.println( "ALDProcessingDAG::tracebackDAG"); } if ( port == null ) { System.err.println( "ALDProcessingDAG::tracebackDAG sourceObjOfHistory not registered!"); throw new ALDProcessingDAGException(ALDProcessingDAGException.DAGExceptionType.INTERNAL_TRACING_ERROR,null); } if ( port instanceof ALDDataPort ) { // we cannot find an opnode for a data port, thus the history will only be this data port // this will probably never happen, as we can not find a port associated to this object allDataports.add( (ALDDataPort)ALDOperator.portHashAccess.getHistoryLink(sourceObjOfHistory) ); } else if ( port instanceof ALDOpNodePort ) { // either an output port or an input port of a complete opNode ALDOpNode opNode = ((ALDOpNodePort)port).getOpNode(); // this does not work across threads /* if ( historyType == HistoryType.COMPLETE && ignoreHiding) { ALDOpNode toplevelOpNode = opNode; while ( toplevelOpNode != null && ! isToplevelOpnode( toplevelOpNode) ) { toplevelOpNode = toplevelOpNode.getParent(); } if ( toplevelOpNode == null ) fatal("tracebackDAG can not find toplevel opnode"); visitOpNodeComplete( toplevelOpNode, 0); } else { */ // traverse bottom up beginning at the port of sourceObjOfHistory // input ports of the first opnode of sourceObjOfHistory which // we have to trace back Vector<ALDInputPort> inputPorts; if ( ! isHiddenOpnode( opNode)) { if ( port instanceof ALDInputPort ) { if ( ! isCompleteOpnode( opNode) ) { // we want to traceback an input port according to data dependency // add the opNode and traceback this input port allOpNodes.add( opNode); inputPorts = new Vector<ALDInputPort> (); inputPorts.add( (ALDInputPort)port); } else { inputPorts = visitOpNodeComplete( opNode, 0); } } else { // visit the opNode with port as interesting output port inputPorts = visitOpNode( opNode, (ALDOutputPort)port, 0); } // trace back the sibblings and parent if (opNode != null) findSibblingsAndParent( opNode, inputPorts, 0); } else { // empty history as the opNode is hidden } } else { fatal( "tracebackDAG illegal port type of port " + port); } // clone of opNode vector used to generate unique Ids for opnodes opNodesClone = (Vector<ALDOpNode>)(allOpNodes.clone()); if ( debug ) { System.out.println( ">>>>>>>>>>>>> opNodes found"); Iterator<ALDOpNode> oItr = allOpNodes.iterator(); while ( oItr.hasNext() ) { oItr.next().print(); } System.out.println( ">>>>>>>>>>>>> edges found"); Iterator<ALDEdge> eItr = allEdges.iterator(); while ( eItr.hasNext() ) { eItr.next().print( " "); } System.out.println( ">>>>>>>>>>>>> data found"); Iterator<ALDDataPort> dItr = allDataports.iterator(); while ( dItr.hasNext() ) { System.out.println( dItr.next()); } } } /** Find all sibblings and the parent of opNode. If history is of type data dependency, * then find everything on which the inputPorts given as arguments (which are input ports of opNode) * depend on. If complete then visit all childs of the opNodes parent completey. * <p> * findSibblingsAndParent invokes itself recursively using * the input ports of the parent on which the history of the parent depends on. * These are all input ports if we visit the parent completely, otherwise * the data dependencies of the inputPorts. * The recursion terminates at the toplevel operator. */ private void findSibblingsAndParent( ALDOpNode opNode, Vector<ALDInputPort> inputPorts, int depth) throws ALDProcessingDAGException { if ( debug ) System.out.println( "findSibblingsAndParent for " + opNode.getName() + " and inputPorts " + inputPorts); // ALDToplevelOperator has no sibblings and parent if ( isToplevelOpnode( opNode) ) return; ALDOpNode parentOpNode = opNode.getParent(); //TODO potential check; are inputPorts ports of opNode? // the inputports of the parent, on which the history depends on Vector<ALDInputPort> parentsInputports = new Vector<ALDInputPort>(); if ( isCompleteOpnode( parentOpNode) && ! isHiddenOpnode( parentOpNode) ) { visitOpNodeComplete( parentOpNode, depth-1); // and go one level up for all input ports of the parent ALDInputPort[] inputPortsArray = parentOpNode.getInputPorts(); for ( int i=0 ; i < inputPortsArray.length ; i++ ) { parentsInputports.add( inputPortsArray[i]); } findSibblingsAndParent( parentOpNode, parentsInputports, depth-1); } else { // either the parent is hidden thus not visited or it is uncomplete // thus we search for data dependencies of the input ports of opNode // output ports of opNode or sibbligs of opNode which we still have to trace back LinkedList<ALDOutputPort> outputPortToTraceback = new LinkedList<ALDOutputPort>(); // trace back each port of inputPorts // origin may be input port of parentNode (added to parentsInputports as a sid effect) // an output port of a sibbling (added to outputPortToTraceback as a sid effect) // or a data port (register directely) handleInputPorts( parentOpNode, inputPorts, parentsInputports, outputPortToTraceback); // now trace back all output ports of opNode and its sibblings // according to data dependency while ( outputPortToTraceback.size() > 0) { ALDOutputPort port = outputPortToTraceback.pop(); handleInputPorts( parentOpNode, visitOpNode( port.getOpNode(), port, depth), parentsInputports, outputPortToTraceback); } if ( debug ) System.out.println( "findSibblingsAndParent found parentsInputports " + parentsInputports); // as the parent is either hidden or uncomplete // we have to visit it only if it is not hidden and we found // at least one input port of the parent on which the given inputPorts depend on if ( parentsInputports.size() > 0 && ! isHiddenOpnode( parentOpNode) ) { // probably this should never happen if ( ! allOpNodes.contains( parentOpNode) ) { allOpNodes.add( parentOpNode); parentOpNode.setDepth( depth-1); findSibblingsAndParent( parentOpNode, parentsInputports, depth-1); } } } } /** Trace back each port of inputPorts which are input ports of a child of parentOpNode. * If the origin of such an input port is of type input port, then it is a port * of parentOpNode and added to parentsInputports. * (As this are the input ports detected according to data dependency.) * If it is of type output port, then is is a port of a sibbling and needs to * be traced back if the sibbling is not hidden, thus added to outputPortToTraceback. * If it is a data port is is just registered. */ private void handleInputPorts( ALDOpNode parentOpNode, Vector<ALDInputPort> inputPorts, Vector<ALDInputPort> parentsInputports, LinkedList<ALDOutputPort> outputPortToTraceback) throws ALDProcessingDAGException { if ( debug ) System.out.println( " handleInputPorts for parentOpNode " + parentOpNode.getName() + "and inputPorts " + inputPorts); Iterator<ALDInputPort> pItr = inputPorts.iterator(); while ( pItr.hasNext() ) { ALDInputPort port = pItr.next(); ALDPort origin = port.getOrigin(); if ( origin != null ) { if ( origin instanceof ALDDataPort ) { if ( ! allDataports.contains( port) ) { register( parentOpNode, (ALDDataPort)origin); allDataports.add( (ALDDataPort)origin); } allEdges.add( new ALDEdge( origin, port)); } else if ( origin instanceof ALDInputPort ) { // we allow different parents if child of toplevel opnode to // find opnodes in other threads if ( ((ALDInputPort)origin).getOpNode() != parentOpNode && ! isToplevelOpnode( parentOpNode) ) { inconsistent( "handleInputPorts origin is of type ALDInputPort but its opNode != parentOpNode"); } else { if ( ! isHiddenOpnode( parentOpNode) ) { parentsInputports.add( (ALDInputPort)origin); allEdges.add( new ALDEdge( origin, port)); } } } else if ( origin instanceof ALDOutputPort ) { // we allow different parents if child of toplevel opnode to // find opnodes in other threads if ( ((ALDOutputPort)origin).getOpNode().getParent() != parentOpNode && ! isToplevelOpnode( parentOpNode) ) { inconsistent( "handleInputPorts origin is of type ALDOutputPort but the parent of its opNode != parentOpNode"); } else { if ( ! isHiddenOpnode( ((ALDOutputPort)origin).getOpNode()) ) { outputPortToTraceback.push( (ALDOutputPort)origin); allEdges.add( new ALDEdge( origin, port)); } } } else { fatal( "handleInputPorts origin is of illegal type: " + origin); } } } } /** Visit the opNode either completely or according to data dependency as define by the opNode * and the globale state of the processing DAG. * If history is according to data dependency then trace back the history * of portOfInterest which is an output port of opNode. * * @return Input ports of opNode on which its history depends on. These are all input ports for * a complete history, otherwise the input ports, on which portOfInterest depends on. */ private Vector<ALDInputPort> visitOpNode( ALDOpNode opNode, ALDOutputPort portOfInterest, int depth) throws ALDProcessingDAGException { if ( debug ) System.out.println( " ALDProcessingDAG::visitOpNode visit " + opNode.getName() + " for port " + portOfInterest); if ( isCompleteOpnode( opNode) ) return visitOpNodeComplete( opNode, depth); else return visitOpNodeDatadependency( opNode, portOfInterest, depth); } /** Visit the opNode detecting all inner children and data ports on which * the output port of interest portOfInterest depends on. * Add the opNode to the processing DAG as well as the detected children and * dataports. Recursively visit the detected children of this opNode. * The method assumes, that opNode is not hidden. * * @return Input ports of this opNode on which the output port of interest portOfInterest * depends on. */ private Vector<ALDInputPort> visitOpNodeDatadependency( ALDOpNode opNode, ALDOutputPort portOfInterest, int depth) throws ALDProcessingDAGException { if ( debug ) System.out.println( " ALDProcessingDAG::visitOpNodeDatadependency visit " + opNode.getName() + " for port " + portOfInterest); if ( isHiddenOpnode( opNode) ) fatal( "visitOpNodeDatadependency opNode " + opNode.getName() + " is hidden"); // TODO potential check // ##if ( ! outputPorts.contains( portOfInterest) ) // ## fatal( "visitOpNodeDatadependency port of interest is no output port of " + opNode.getName()); Vector<ALDInputPort> inputPorts = new Vector<ALDInputPort>(); // if we have already traced back the portOfInterest just return an empty vector of input ports if ( opNodePortsTraced.contains( portOfInterest) ) return inputPorts; // the opNode may have already be discovered, but for other ports of interest if ( ! allOpNodes.contains( opNode) ) { allOpNodes.add( opNode); opNode.setDepth( depth); } // output ports of opNodes or children of opNode on which the port of interest depends on LinkedList<ALDOpNodePort> portsToVisit = new LinkedList<ALDOpNodePort>(); portsToVisit.add( portOfInterest); while( portsToVisit.size() > 0 ) { ALDOpNodePort port = portsToVisit.pop(); // Port is either an OutputPort of opNode or an InputPort of a child of opNode. // If the origin of port is of type OutputPort, than a potentially new source child is found // and recursively visited. // If it is of type DataPort, than a dataport of opNode is found and registered. if ( ! opNodePortsTraced.contains( port) ) { opNodePortsTraced.add( portOfInterest); ALDPort origin = port.getOrigin(); if ( debug ) System.out.println( " ALDProcessingDAG::visitOpNodeDatadependency trace back port " + port + " with origin " + origin); if ( origin != null ) { if ( origin instanceof ALDInputPort ) { if ( ((ALDInputPort)origin).getOpNode() != opNode ) { inconsistent( "visitOpNodeDatadependency origin is of type ALDInputPort but has different parent then opNode: " + ((ALDInputPort)origin).getOpNode() + " vs " + opNode); } else { allEdges.add( new ALDEdge( origin, port)); inputPorts.add( (ALDInputPort)origin); } } else if ( origin instanceof ALDOutputPort ) { ALDOpNode originatingOpNode = ((ALDOutputPort)origin).getOpNode(); if ( originatingOpNode.getParent() != opNode ) { inconsistent( "visitOpNodeDatadependency origin is of type ALDOutputPort but its opNode has different parent then opNode" + originatingOpNode.getParent() + " vs " + opNode); } else { if ( ! isHiddenOpnode( originatingOpNode) ) { allEdges.add( new ALDEdge( origin, port)); portsToVisit.addAll( visitOpNode( originatingOpNode, (ALDOutputPort)origin, depth+1)); } } } else if ( origin instanceof ALDDataPort ) { if ( ! allDataports.contains( origin) ) { register( opNode, (ALDDataPort)origin); allDataports.add( (ALDDataPort)origin); } allEdges.add( new ALDEdge( origin, port)); } else { fatal( "visitOpNodeDatadependency illegal orign type: " + origin); } } } } if ( debug ) System.out.println( " ALDProcessingDAG::visitOpNodeDatadependency returning " + inputPorts); return inputPorts; } /** Completely visits this opNode. Add the opNode to the processing DAG as well as the directly * included dataports. All edges within this opNode are also added if not connected to * a hidden opnode. * Recursively visit all non hidden children of this opNode. * We assume that this method is never called for a hidden opNode. * * @return All input ports a the history for a complete node depends on all inputs. */ private Vector<ALDInputPort> visitOpNodeComplete( ALDOpNode opNode, int depth) throws ALDProcessingDAGException { if ( isHiddenOpnode( opNode) ) fatal( "visitOpNodeComplete opNode " + opNode.getName() + " is hidden"); if ( debug ) System.out.println( " ALDProcessingDAG::visitOpNodeComplete visit " + opNode.getName()); if ( ! allOpNodes.contains( opNode) ) { allOpNodes.add( opNode); opNode.setDepth( depth); // add edges connecting to the output ports of this opNode // and register data which are the source of an edge ALDOutputPort[] outputPorts = opNode.getOutputPorts(); for ( int i=0 ; i < outputPorts.length ; i++ ) { handlePort( opNode, outputPorts[i]); } Iterator<ALDOpNode> oItr = opNode.getDirectlyRegisteredChildern().iterator(); while ( oItr.hasNext() ) { ALDOpNode childOpNode = oItr.next(); // visit non hidden children if ( ! allOpNodes.contains( childOpNode) && ! isHiddenOpnode( childOpNode)) { visitOpNodeComplete( childOpNode, depth+1); } // it might be that a child was visited before its parent opNode // and we still have connect edges and regsiter data parts if ( ! isHiddenOpnode( childOpNode)) { // add edges connecting to the input ports of this child // and register data which are the source of an edge ALDInputPort[] inputPorts = childOpNode.getInputPorts(); for ( int i=0 ; i < inputPorts.length ; i++ ) handlePort( opNode, inputPorts[i]); } } } // return all input ports Vector<ALDInputPort> inputPorts = new Vector<ALDInputPort>(); ALDInputPort[] inputPortsArray = opNode.getInputPorts(); for ( int i=0 ; i < inputPortsArray.length ; i++ ) { inputPorts.add( inputPortsArray[i]); } return inputPorts; } /** Handle a port to register the incoming edge and to potentially register dataport to opNode. * Port is either an OutputPort of opNode or an InputPort of a child of opNode. * If it is of type DataPort, than a dataport of opNoe is found an registered. * A edge to an output port of a child opnode is added only, if this child i snot hidden. */ private void handlePort( ALDOpNode opNode, ALDPort port ) throws ALDProcessingDAGException { ALDPort origin = port.getOrigin(); if ( origin != null ) { if ( origin instanceof ALDInputPort ) { // TODO potential check: origin.getOpNode == opNode if ( ! isHiddenOpnode( ((ALDInputPort)origin).getOpNode()) ) allEdges.add( new ALDEdge( origin, port)); } else if ( origin instanceof ALDOutputPort ) { ALDOpNode childOpNode = ((ALDOutputPort)origin).getOpNode(); // TODO potential check: if childOpNode has already a depth, childOpNode.getDepth = depth+1 if ( ! isHiddenOpnode( childOpNode) ) allEdges.add( new ALDEdge( origin, port)); } else if ( origin instanceof ALDDataPort ) { // TODO potential check: dataport should be discovered only once if ( ! allDataports.contains( origin) ) { register( opNode, (ALDDataPort)origin); allDataports.add( (ALDDataPort)origin); } allEdges.add( new ALDEdge( origin, port)); } else { fatal( "handlePort origin " + origin + " is of unknown type"); } } } /** Is this an opNode of the toplevel operator. */ private boolean isToplevelOpnode( ALDOpNode opNode) { return opNode.getName().equals( "ALDToplevelOperator"); } /** Is a complete history to be created for this opNode? * @return true if global history type/mode is COMPLETE, or * global history type/mode is OPNODETYPE and the opNode is complete. * Otherwise false. * The only exceptions are opnodes of the ALDToplevelOperator as we have to allow * to find the way to operators in other threads. */ private boolean isCompleteOpnode( ALDOpNode opNode) { if ( isToplevelOpnode( opNode) ) return false; else return (this.historyType == HistoryType.COMPLETE) || (this.historyType == HistoryType.OPNODETYPE && opNode.completeDAG); } /** Should we hide this opnode. * @return true if opNode is hidden and global ignore of hiding is false. */ private boolean isHiddenOpnode( ALDOpNode opNode) { return (! ignoreHiding) && ( opNode.getHidingMode() == HidingMode.HIDDEN ) ; } /** Register an opNode as child to its parents if not already found directly or * indirectly before. * This is a child found through connections between ports and not during execution * of operators. */ private void register( ALDOpNode parentOpNode, ALDOpNode opNode) { if ( opNode.getParent() == null ) { opNode.setParent( parentOpNode); parentOpNode.addChild( opNode); } else { if ( opNode.getParent() != parentOpNode ) System.out.println( "ALDProcessingDAG::register register parent for " + opNode + " again with different parent"); } } /** Register the dataport as directly included in opNode. */ private void register( ALDOpNode opNode, ALDDataPort dataport ) { if ( ! opNode.getIncludedData().contains( dataport ) ) opNode.addData( dataport); } // ===================== convert to graphml structure /** Create the processing history for <code>sourceObjOfHistory</code> and convert to graphml object. * The complete history according is created. This method is * equivalent to <code>createGraphmlDocument( sourceObjOfHistory, HistoryType.COMPLETE, false)</code>. * * @param sourceObjOfHistory for which the processing history to be created. * @return Processing history as graphml object or null if no history is available. */ public GraphmlDocument createGraphmlDocument( Object sourceObjOfHistory ) throws ALDProcessingDAGException { return createGraphmlDocument( sourceObjOfHistory, HistoryType.COMPLETE, false); } /** Create the processing history for <code>sourceObjOfHistory</code> and convert to graphml object. * This method is equivalent to <code>createGraphmlDocument( sourceObjOfHistory, historyType, false)</code>. * * @param sourceObjOfHistory for which the processing history to be written. * @param historyType type/moude of history to be created. * @return Processing history as graphml object or null if no history is available. */ public GraphmlDocument createGraphmlDocument( Object sourceObjOfHistory, HistoryType historyType ) throws ALDProcessingDAGException { return createGraphmlDocument( sourceObjOfHistory, historyType, false); } /** Create the processing history for <code>sourceObjOfHistory</code> and convert to graphml object. * * @param sourceObjOfHistory for which the processing history to be written. * @param historyType type/mode of history to be created. * @param ignoreHiding if true hiding of opnodes is ignores, i.e. all opnodes * added to the history. * @return Processing history as graphml object or null if no history is available. */ public GraphmlDocument createGraphmlDocument( Object sourceObjOfHistory, HistoryType historyType, boolean ignoreHiding) throws ALDProcessingDAGException { if ( debug ) System.out.println( "ALDProcessingDAG::createGraphmlDocument START for " + sourceObjOfHistory); this.ignoreHiding = ignoreHiding; this.historyType = historyType; tracebackDAG( sourceObjOfHistory); // this is the overall graphml document GraphmlDocument graphmlDoc = GraphmlDocument.Factory.newInstance(); // setup the graphml element GraphmlType graphMl = graphmlDoc.addNewGraphml(); graphMl.setDesc( "Alida Processing History"); // and the keys addKey( graphMl, "aldName", KeyForType.NODE, "aldName", KeyTypeType.STRING); addKey( graphMl, "aldNodeType", KeyForType.NODE, "aldNodeType", KeyTypeType.STRING); addKey( graphMl, "aldExplanation", KeyForType.NODE, "aldExplanation", KeyTypeType.STRING); addKey( graphMl, "aldClassname", KeyForType.NODE, "aldClassname", KeyTypeType.STRING); addKey( graphMl, "aldProps", KeyForType.NODE, "aldProperties", KeyTypeType.STRING); addKey( graphMl, "aldParas", KeyForType.NODE, "aldParameteres", KeyTypeType.STRING); addKey( graphMl, "aldParasXML", KeyForType.NODE, "aldParameteresAsXml", KeyTypeType.STRING); addKey( graphMl, "aldOutput", KeyForType.GRAPH, "aldOutput", KeyTypeType.STRING); addKey( graphMl, "aldXml", KeyForType.GRAPH, "aldXmlHistory", null); addKey( graphMl, "aldEdgeType", KeyForType.EDGE, "aldEdgeType", KeyTypeType.STRING); // top level graph, this is the actual graph containing the history GraphType graph = graphMl.addNewGraph(); graph.setEdgedefault( GraphEdgedefaultType.DIRECTED); // add result object for which this processing history was constrcted as attribute to the // top level graph addAttrToGraph( graph, "aldResultObject", getPortId( ALDOperator.portHashAccess.getHistoryLink(sourceObjOfHistory))); // determine the minimal depth of all opNodes found int minDepth = 1; for ( int i=0 ; i < allOpNodes.size() ; i++ ) { if ( allOpNodes.elementAt(i).getDepth() < minDepth ) minDepth = allOpNodes.elementAt(i).getDepth(); } // add all opNodes at minDepth and recursively their children opNodesInGraph = new Vector<ALDOpNode>(); while ( allOpNodes.size() > 0 ) { boolean found = false; for ( int i=0 ; i < allOpNodes.size() ; i++ ) { if ( allOpNodes.elementAt(i).getDepth() == minDepth ) { outputOpNode( allOpNodes.elementAt(i), graph); found = true; // we restart, as we have modified opNodes during outputOpNode break; } } // we did not find any node at minDepth if ( ! found ) break; } if ( allOpNodes.size() > 0 ) { System.err.println( "ALDProcessingDAG::createGraphmlDocument WARNING: leftover orphan opNodes"); for ( int i=0 ; i < allOpNodes.size() ; i++ ) { allOpNodes.elementAt(i).print(); } } // ALDdata which have not already been written Iterator<ALDDataPort> dItr = allDataports.iterator(); while ( dItr.hasNext() ) { ALDDataPort d = dItr.next(); if ( ! writtenDataports.contains( d) ) { if ( debug ) System.out.println( "ALDProcessingDAG::createGraphmlDocument remaining ALDData " + d); addDataPortToGraphml( graph, d); writtenDataports.add( d); } } // edges int idx = 0; Iterator<ALDEdge> eItr = allEdges.iterator(); while ( eItr.hasNext() ) { ALDEdge e = eItr.next(); int n = allEdges.indexOf( e); addEdge( graph, e.getSourcePort(), e.getTargetPort(), idx); idx++; } return graphmlDoc; } // ===================== Helper methods for Write DAG /** Output <code>opNode</code> and it ports, and recursively its children to the <code>graph</code>. * An opNode is represented by an node and contains a (graphml) graph as * is containts nested ports and further opNodes. */ private void outputOpNode( ALDOpNode opNode, GraphType graph) throws ALDProcessingDAGException { if ( debug ) System.out.println( "ALDProcessingDAG::outputOpNode start output for " + opNode.getName() + "<" + opNode + ">"); allOpNodes.remove( opNode); opNodesInGraph.add( opNode); // graph of this opNode which contains its ports and child opNodes GraphType nestedG; if ( isToplevelOpnode( opNode) ) { // toplevel opnodes are not to incuded into the preocssing history, // as they are dummies nestedG = graph; } else { NodeType node = addOpNodeToGraph( graph, opNode); nestedG = node.addNewGraph(); nestedG.setEdgedefault( GraphEdgedefaultType.DIRECTED); // add input ports to the nested graph of opNode ALDInputPort[] inputPorts = opNode.getInputPorts(); for ( int p=0 ; p < inputPorts.length ; p++ ) { // port as graphml nodes addOpNodePortToGraphml( nestedG, inputPorts[p]); } // add output ports to the nested graph of opNode ALDOutputPort[] outputPorts = opNode.getOutputPorts(); for ( int p=0 ; p < outputPorts.length ; p++ ) { // port as graphml nodes addOpNodePortToGraphml( nestedG, outputPorts[p]); } } // add children found during traceback to the nested graph of opNode //##Vector<ALDOpNode> children = opNode.getChildren(); Vector<ALDOpNode> children = opNode.getDirectlyRegisteredChildern(); Iterator<ALDOpNode> oItr = children.iterator(); while (oItr.hasNext()) { ALDOpNode child = oItr.next(); if ( allOpNodes.contains( child) ) { allOpNodes.remove( child); outputOpNode( child, nestedG); } } // included data Iterator<ALDDataPort> dItr = opNode.getIncludedData().iterator(); while ( dItr.hasNext() ) { ALDDataPort d = dItr.next(); addDataPortToGraphml( nestedG, d); // delete from allDataports, to avoid multiple graph nodes for this ALDData writtenDataports.add( d); } } /** Add an graphml edge to <code>graph</code> with index <code>idx</code> */ private void addEdge( GraphType graph, ALDPort sourcePort, ALDPort targetPort, int idx) throws ALDProcessingDAGException { // check if source and tragets are in the graph if ( sourcePort instanceof ALDDataPort && ! writtenDataports.contains( (ALDDataPort)sourcePort) ) { inconsistent( "addEdge sourcePort is of type ALDDataPort but not in allDataports: "); sourcePort.print( " "); return; } if ( sourcePort instanceof ALDOpNodePort && ! opNodesInGraph.contains( ((ALDOpNodePort)sourcePort).getOpNode()) ){ inconsistent( "addEdge sourcePort is of type ALDOpNodePort but its opNode not in opNodesInGraph"); sourcePort.print( " "); return; } if ( ! opNodesInGraph.contains( ((ALDOpNodePort)targetPort).getOpNode()) ) { inconsistent( "addEdge targetPort is of type ALDOpNodePort but its opNode not in opNodesInGraph for: "); targetPort.print( " "); return; } EdgeType edge = graph.addNewEdge(); edge.setId( getGraphName(0) + idSeparator + "Edge" + idx); // ports as graphml ports edge.setSource( getPortId( sourcePort)); edge.setTarget( getPortId( targetPort)); //addAttrToEdge( edge, "arrow", "Target"); edge.setDirected( true); } // =========================== histories ========================== /** Add the complete history of an object represented by a data port, * which was read from a graphml file, to a graph object. * Add all nodes and edges (besides the toplevel graph) from the * history to the graph object at the toplevel of the graph and * connect the output port to the node representing the data port. * Rename IDs in the history which is connected. * * @param graph Graph to add the history read from file * @param dataport */ private void addGraphmlHistory( GraphType graph, ALDDataPort dataport) throws ALDProcessingDAGException { if ( debug ) System.out.println( "ALDProcessingDAG::addGraphmlHistory"); if ( dataport.getGraphmlHistory() == null ) return; GraphmlType history = dataport.getGraphmlHistory(); GraphType hGraph = GraphmlHelper.getToplevelGraph( history); if ( hGraph == null ) return; // rename Ids in history graphml graphIndex += GraphmlHelper.renameGraphIds( hGraph, graphIndex+1); int oldNumNodes = graph.sizeOfNodeArray(); for ( int n = 0 ; n < hGraph.sizeOfNodeArray() ; n++ ) { NodeType node = hGraph.getNodeArray( n); if ( debug ) System.out.println( "ALDProcessingDAG::addGraphmlHistory add Node ");// + node); graph.insertNewNode( oldNumNodes+n); graph.setNodeArray( oldNumNodes+n, node); } int oldNumEdges = graph.sizeOfEdgeArray(); for ( int e = 0 ; e < hGraph.sizeOfEdgeArray() ; e++ ) { EdgeType edge = hGraph.getEdgeArray( e); if ( debug ) System.out.println( "ALDProcessingDAG::addGraphmlHistory add Edge ");// + edge); graph.insertNewEdge( oldNumEdges+e); graph.setEdgeArray( oldNumEdges+e, edge); } int oldNumDatas = graph.sizeOfDataArray(); for ( int d = 0 ; d < hGraph.sizeOfDataArray() ; d++ ) { DataType gdata = hGraph.getDataArray( d); // add extra edge to connect graphs if ( gdata.getKey().equals("aldResultObject") || gdata.getKey().equals("mtbOutput") ) { EdgeType edge = graph.addNewEdge(); edge.setSource( gdata.newCursor().getTextValue()); edge.setTarget( getPortId( dataport)); edge.setDirected(true); addAttrToEdge( edge, "aldEdgeType", "connectHistories"); if ( debug ) System.out.println( "ALDProcessingDAG::addGraphmlHistory add Edge for Output " + gdata.newCursor().getTextValue() + " --> " + getPortId( dataport)); } } } /** Add the complete history of a ALDData from a generic XML file to a graphml node. * The complete xml tree is put in place without any processing. */ private void addXmlHistory( NodeType node, XmlObject xmlHistory) { if ( debug ) System.out.println( "ALDProcessingDAG::addXmlHistory"); XmlCursor hCursor = xmlHistory.newCursor(); hCursor.toStartDoc(); hCursor.toNextToken(); DataType data = node.addNewData(); data.setKey( "aldXml"); XmlCursor dCursor = data.addNewXmlHistory().newCursor(); dCursor.toEndToken(); hCursor.moveXml( dCursor); } /** Add the parameter hash to to a graphml node. */ private void addParameterHash( NodeType node, ALDOpNode opNode) { if ( debug ) System.out.println( "ALDProcessingDAG::addParameterHash"); XmlCursor hCursor = opNode.getParameterHashAsXml().newCursor(); hCursor.toStartDoc(); hCursor.toNextToken(); DataType data = node.addNewData(); data.setKey( "aldParasXML"); XmlCursor dCursor = data.addNewParameterWrapper().newCursor(); dCursor.toEndToken(); hCursor.copyXml( dCursor); } // ======================== graphml Helper ================================ /** add a key definition to graphml */ private void addKey( GraphmlType graphml, String id, KeyForType.Enum domain, String name, KeyTypeType.Enum keyType) { KeyType key = graphml.addNewKey(); key.setId( id); key.setFor( domain); key.setAttrName( name); key.setAttrType( keyType); } /** Add an opNode to the graph of the enclosing opNode. * Add a node to the graph, set its attributes and properties. * Add also also the parameters of the opNode with its values. */ private NodeType addOpNodeToGraph( GraphType graph, ALDOpNode opNode) { NodeType node = graph.addNewNode(); node.setId( getOpNodeId( opNode)); addAttrToNode( node, "aldName", opNode.getName()); addAttrToNode( node, "aldNodeType", "OpNode"); // parameteres DataType data = node.addNewData(); data.setKey( "aldParas"); PropertyListType props = data.addNewProperties(); // commented for the moment, as it did crash //addParameterHash( node, opNode); Enumeration<String> keys = opNode.getParameterKeys(); while ( keys.hasMoreElements() ) { String key = keys.nextElement(); String value = opNode.getParameter( key); PropertyType prop = props.addNewProperty(); prop.setKey( key); prop.setValue( value); } // properties data = node.addNewData(); data.setKey( "aldProps"); props = data.addNewProperties(); PropertyType prop = props.addNewProperty(); prop.setKey( "classname"); prop.setValue( opNode.getOperatorClass().getSimpleName()); if ( opNode.getOperatorClass().getPackage() != null ) { prop = props.addNewProperty(); prop.setKey( "package"); prop.setValue( opNode.getOperatorClass().getPackage().getName()); } prop = props.addNewProperty(); prop.setKey( "version"); prop.setValue( opNode.getVersion()); return node; } /** Add a data port to the graphML graph */ private NodeType addDataPortToGraphml( GraphType graph, ALDDataPort dataport) throws ALDProcessingDAGException { NodeType node = graph.addNewNode(); node.setId( getDataPortId( dataport)); String loc = dataport.getLocation(); if ( loc != null ) { addAttrToNode( node, "aldName", ALDFilePathManipulator.removeLeadingDirectories( loc)); } if ( dataport.getGraphmlHistory() != null || loc != null ) { addAttrToNode( node, "aldNodeType", "DataPortFromFile"); addGraphmlHistory( graph, dataport); } else { addAttrToNode( node, "aldName", ""); addAttrToNode( node, "aldNodeType", "DataPort"); } DataType data = node.addNewData(); data.setKey( "aldProps"); PropertyListType props = data.addNewProperties(); Enumeration<String> keys = dataport.getPropertyKeys(); while ( keys.hasMoreElements() ) { String key = keys.nextElement(); String value = dataport.getProperty( key); PropertyType prop = props.addNewProperty(); prop.setKey( key); prop.setValue( value); } return node; } /** Add a opNode port object to the graphML graph */ private NodeType addOpNodePortToGraphml( GraphType graph, ALDOpNodePort port) throws ALDProcessingDAGException { NodeType node = graph.addNewNode(); node.setId( getPortId( port)); ALDOpNode opNode = port.getOpNode(); String descriptorName = port.getDescriptorName(); try { if ( port instanceof ALDOutputPort ) { addAttrToNode( node, "aldName", descriptorName); addAttrToNode( node, "aldNodeType", "OutputPort"); addAttrToNode( node, "aldExplanation", ((ALDOpNodePort)port).getExplanation()); addAttrToNode( node, "aldClassname", ((ALDOpNodePort)port).getClassname()); ALDOutputPort outputPort = (ALDOutputPort)port; // properties DataType data = node.addNewData(); data.setKey( "aldProps"); PropertyListType props = data.addNewProperties(); Enumeration<String> keys = outputPort.getPropertyKeys(); while ( keys.hasMoreElements() ) { String key = keys.nextElement(); String value = outputPort.getProperty( key); PropertyType prop = props.addNewProperty(); prop.setKey( key); prop.setValue( value); } } else { // InputPort addAttrToNode( node, "aldName", descriptorName); addAttrToNode( node, "aldNodeType", "InputPort"); addAttrToNode( node, "aldExplanation", ((ALDOpNodePort)port).getExplanation()); addAttrToNode( node, "aldClassname", ((ALDOpNodePort)port).getClassname()); } } catch (Exception e) { e.printStackTrace(); } return node; } // ======= data attributes =========================== /** Add an attribute to a graphml element */ private void addAttrToGraphml( GraphmlType graphml, String key, String value) { DataType data = graphml.addNewData(); data.setKey( key); data.set( XmlString.Factory.newValue( value)); } /** Add an attribute to a graphml graph */ private void addAttrToGraph( GraphType graph, String key, String value) { DataType data = graph.addNewData(); data.setKey( key); data.set( XmlString.Factory.newValue( value)); } /** Add an attribute to a graphml node */ private void addAttrToNode( NodeType node, String key, String value) { DataType data = node.addNewData(); data.setKey( key); data.set( XmlString.Factory.newValue( value)); } /** Add an attribute to a graphml edge */ private void addAttrToEdge( EdgeType edge, String key, String value) { DataType data = edge.addNewData(); data.setKey( key); data.set( XmlString.Factory.newValue( value)); } /** Add an attribute to a graphml port */ private void addAttrToPort( PortType port, String key, String value) { DataType data = port.addNewData(); data.setKey( key); data.set( XmlString.Factory.newValue( value)); } // ======================== IDs ================================ /** Remove the graphIP including idSeparator at the beginning of an id */ static String removeGraphId( String id) { return id.substring( id.indexOf(idSeparator) +1); } /** Get the graphIP at the beginning of an id up (exculing) idSeparator */ static String getGraphId( String id) { return id.substring( 0, id.indexOf(idSeparator)); } /** Return an ID used in graphml for graph */ static String getGraphName( int idx) { return new String( "ALDDAG" + idx); } /** Return an ID used in graphml an opNode */ private String getOpNodeId( ALDOpNode opNode) { return getGraphName( 0) + idSeparator + "OpNode" + opNodesClone.indexOf( opNode); } /** Return an ID used in graphml for a data port */ private String getDataPortId( ALDDataPort dataport) { return getGraphName( 0) + idSeparator + "DataPort" + allDataports.indexOf( dataport); } /** Return an ID used in graphml for a port */ private String getPortId( ALDPort p) throws ALDProcessingDAGException { if ( p instanceof ALDInputPort ) { ALDOpNodePort op = (ALDOpNodePort)p; return getGraphName( 0) + idSeparator + "InputPort" + op.getPortIndex() + "-of-" + removeGraphId( getOpNodeId( op.getOpNode())); } else if ( p instanceof ALDOutputPort ) { ALDOpNodePort op = (ALDOpNodePort)p; return getGraphName( 0) + idSeparator + "OutputPort" + op.getPortIndex() + "-of-" + removeGraphId( getOpNodeId( op.getOpNode())); } else if ( p instanceof ALDDataPort ) { ALDDataPort dp = (ALDDataPort)p; return getDataPortId( dp); } else { System.err.println( "ALDProcessingDAG::getPortId PANIC unkwon Porttype"); throw new ALDProcessingDAGException(ALDProcessingDAGException.DAGExceptionType.INTERNAL_TRACING_ERROR,null); } } /** Output fatal error to stderr and throw an exception */ private void fatal( String msg) throws ALDProcessingDAGException { System.err.println( "FATAL ERROR in ALDProcessingDAG::" + msg); throw new ALDProcessingDAGException(ALDProcessingDAGException.DAGExceptionType.INTERNAL_TRACING_ERROR,null); } /** Output error to stderr */ private void inconsistent( String msg) { System.err.println( "ERROR in ALDProcessingDAG::" + msg); } }