/* * Copyright (C) 2012 Jason Gedge <http://www.gedge.ca> * * This file is part of the OpGraph project. * * 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/>. */ package ca.gedge.opgraph.app.util; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import javax.swing.JComponent; import ca.gedge.opgraph.InputField; import ca.gedge.opgraph.OpLink; import ca.gedge.opgraph.OpGraph; import ca.gedge.opgraph.OpNode; import ca.gedge.opgraph.OutputField; import ca.gedge.opgraph.app.extensions.NodeMetadata; import ca.gedge.opgraph.app.extensions.NodeSettings; import ca.gedge.opgraph.app.extensions.Note; import ca.gedge.opgraph.app.extensions.NoteComponent; import ca.gedge.opgraph.app.extensions.Notes; import ca.gedge.opgraph.dag.CycleDetectedException; import ca.gedge.opgraph.dag.VertexNotFoundException; import ca.gedge.opgraph.exceptions.ItemMissingException; import ca.gedge.opgraph.extensions.CompositeNode; import ca.gedge.opgraph.extensions.Publishable; import ca.gedge.opgraph.extensions.Publishable.PublishedInput; import ca.gedge.opgraph.extensions.Publishable.PublishedOutput; /** * Helper methods for graphs. * * TODO Need to generalize cloning of extensions. This would be best done through implementing * Cloneable and Object#clone() for everything, and throwing up a warning dialog whenever * an extension is encountered which doesn't implement Cloneable. */ public class GraphUtils { /** Logger */ private final static Logger LOGGER = Logger.getLogger(GraphUtils.class.getName()); /** * Clone a node along with {@link NodeSettings}, {@link NodeMetadata}, * {@link CompositeNode}, and {@link Publishable} extensions cloned. * * @param node the node to clone * * @return the cloned node, or <code>null</code> if the node could not be cloned * * @throws NullPointerException if node is <code>null</code> */ public static OpNode cloneNode(OpNode node) { if(node == null) throw new NullPointerException(); final Class<? extends OpNode> nodeClass = node.getClass(); try { final OpNode newNode = nodeClass.newInstance(); newNode.setName(node.getName()); // copy node settings (if available) final NodeSettings nodeSettings = node.getExtension(NodeSettings.class); final NodeSettings newNodeSettings = newNode.getExtension(NodeSettings.class); if(nodeSettings != null && newNodeSettings != null) { newNodeSettings.loadSettings(nodeSettings.getSettings()); } // copy meta data (if available) final NodeMetadata metaData = node.getExtension(NodeMetadata.class); if(metaData != null) { final NodeMetadata newMetaData = new NodeMetadata(metaData.getX(), metaData.getY()); newNode.putExtension(NodeMetadata.class, newMetaData); } // if a composite node, clone graph final CompositeNode compositeNode = node.getExtension(CompositeNode.class); final CompositeNode newCompositeNode = newNode.getExtension(CompositeNode.class); if(compositeNode != null && newCompositeNode != null) { final Map<String, String> nodeMap = new HashMap<String, String>(); final OpGraph graph = compositeNode.getGraph(); final OpGraph newGraph = cloneGraph(graph, null, nodeMap); newCompositeNode.setGraph(newGraph); // setup published fields (if available) final Publishable publishable = node.getExtension(Publishable.class); final Publishable newPublishable = newNode.getExtension(Publishable.class); if(publishable != null && newPublishable != null) { for(PublishedInput pubInput:publishable.getPublishedInputs()) { final OpNode destNode = newGraph.getNodeById(nodeMap.get(pubInput.destinationNode.getId()), false); if(destNode != null) { final InputField destField = destNode.getInputFieldWithKey(pubInput.nodeInputField.getKey()); newPublishable.publish(pubInput.getKey(), destNode, destField); } } for(PublishedOutput pubOutput:publishable.getPublishedOutputs()) { final OpNode srcNode = newGraph.getNodeById(nodeMap.get(pubOutput.sourceNode.getId()), false); if(srcNode != null) { final OutputField srcField = srcNode.getOutputFieldWithKey(pubOutput.nodeOutputField.getKey()); newPublishable.publish(pubOutput.getKey(), srcNode, srcField); } } } } // XXX Other extensions. See note attached to class javadoc. return newNode; } catch (InstantiationException e) { LOGGER.severe(e.getMessage()); } catch (IllegalAccessException e) { LOGGER.severe(e.getMessage()); } return null; } /** * Clone the given graph. * * TODO Deal with cloning custom extensions * * @param graph the graph to clone * @param newGraph graph to modify. If <code>null</code> a new graph will be * created and returned. If not <code>null</code>, the return * value will be the same object as this variable. * @param nodeMap mapping from node id to cloned node id * * @return the cloned graph */ public static OpGraph cloneGraph(OpGraph graph, OpGraph newGraph, Map<String, String> nodeMap) { final OpGraph retVal = (newGraph != null ? newGraph : new OpGraph()); // Clone nodes for(OpNode node : graph.getVertices()) { final OpNode clonedNode = cloneNode(node); nodeMap.put(node.getId(), clonedNode.getId()); retVal.add(clonedNode); } // Clone links for(OpLink link : graph.getEdges()) { final OpNode origSource = link.getSource(); final OpNode newSource = retVal.getNodeById(nodeMap.get(origSource.getId()), false); final OutputField sourceField = newSource.getOutputFieldWithKey(link.getSourceField().getKey()); final OpNode origDest = link.getDestination(); final OpNode newDest = retVal.getNodeById(nodeMap.get(origDest.getId()), false); final InputField destField = newDest.getInputFieldWithKey(link.getDestinationField().getKey()); try { final OpLink newLink = new OpLink(newSource, sourceField.getKey(), newDest, destField.getKey()); retVal.add(newLink); } catch (ItemMissingException e) { LOGGER.severe(e.getMessage()); } catch (VertexNotFoundException e) { LOGGER.severe(e.getMessage()); } catch (CycleDetectedException e) { LOGGER.severe(e.getMessage()); } } // Clone notes final Notes notes = graph.getExtension(Notes.class); if(notes != null) { final Notes newNotes = new Notes(); for(Note note:notes) { final Note newNote = cloneNote(note); newNotes.add(newNote); } retVal.putExtension(Notes.class, newNotes); } // XXX Other extensions. See note attached to class javadoc. return retVal; } /** * Clones the given graph. * * @param graph the graph to clone * * @return the cloned graph */ public static OpGraph cloneGraph(OpGraph graph) { return cloneGraph(graph, null, new HashMap<String, String>()); } /** * Clone a graph note. * * @param note the note to clone * * @return the cloned note */ public static Note cloneNote(Note note) { final Note retVal = new Note(note.getTitle(), note.getBody()); final JComponent oldComp = note.getExtension(JComponent.class); final JComponent newComp = retVal.getExtension(JComponent.class); if(newComp != null && oldComp != null) { newComp.setBackground(oldComp.getBackground()); newComp.setLocation(oldComp.getLocation()); newComp.setPreferredSize(newComp.getPreferredSize()); } return retVal; } }