/* * Copyright (C) 2014 by Array Systems Computing Inc. http://www.array.ca * * 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 org.esa.snap.graphbuilder.rcp.dialogs.support; import com.bc.ceres.binding.dom.DomElement; import com.bc.ceres.binding.dom.XppDomElement; import com.bc.ceres.core.ProgressMonitor; import com.thoughtworks.xstream.io.xml.xppdom.XppDom; import org.apache.commons.math3.util.FastMath; import org.esa.snap.core.gpf.GPF; import org.esa.snap.core.gpf.OperatorSpi; import org.esa.snap.core.gpf.OperatorSpiRegistry; import org.esa.snap.core.gpf.annotations.OperatorMetadata; import org.esa.snap.core.gpf.common.WriteOp; import org.esa.snap.core.gpf.graph.Graph; import org.esa.snap.core.gpf.graph.GraphContext; import org.esa.snap.core.gpf.graph.GraphException; import org.esa.snap.core.gpf.graph.GraphIO; import org.esa.snap.core.gpf.graph.GraphProcessor; import org.esa.snap.core.gpf.graph.Node; import org.esa.snap.core.util.io.FileUtils; import org.esa.snap.core.util.io.SnapFileFilter; import org.esa.snap.engine_utilities.gpf.ReaderUtils; import org.esa.snap.graphbuilder.gpf.ui.OperatorUI; import org.esa.snap.graphbuilder.gpf.ui.OperatorUIRegistry; import org.esa.snap.rcp.util.Dialogs; import javax.swing.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Observable; import java.util.Set; public class GraphExecuter extends Observable { private final GPF gpf; private Graph graph; private GraphContext graphContext = null; private GraphProcessor processor; private String graphDescription = ""; private File lastLoadedGraphFile = null; private final GraphNodeList graphNodeList = new GraphNodeList(); private final static String LAST_GRAPH_PATH = "graphbuilder.last_graph_path"; public enum events {ADD_EVENT, REMOVE_EVENT, SELECT_EVENT, CONNECT_EVENT, REFRESH_EVENT} public GraphExecuter() { gpf = GPF.getDefaultInstance(); graph = new Graph("Graph"); } public List<GraphNode> GetGraphNodes() { return graphNodeList.getGraphNodes(); } public GraphNodeList getGraphNodeList() { return graphNodeList; } public void ClearGraph() { graph = null; graph = new Graph("Graph"); lastLoadedGraphFile = null; graphNodeList.clear(); } public void setSelectedNode(GraphNode node) { if (node == null) return; notifyGraphEvent(new GraphEvent(events.SELECT_EVENT, node)); } /** * Gets the list of operators * * @return set of operator names */ public Set<String> GetOperatorList() { return gpf.getOperatorSpiRegistry().getAliases(); } public boolean isOperatorInternal(String alias) { final OperatorSpiRegistry registry = gpf.getOperatorSpiRegistry(); final OperatorSpi operatorSpi = registry.getOperatorSpi(alias); final OperatorMetadata operatorMetadata = operatorSpi.getOperatorClass().getAnnotation(OperatorMetadata.class); return !(operatorMetadata != null && !operatorMetadata.internal()); } public String getOperatorCategory(String alias) { final OperatorSpiRegistry registry = gpf.getOperatorSpiRegistry(); final OperatorSpi operatorSpi = registry.getOperatorSpi(alias); final OperatorMetadata operatorMetadata = operatorSpi.getOperatorClass().getAnnotation(OperatorMetadata.class); if (operatorMetadata != null) return operatorMetadata.category(); return ""; } public GraphNode addOperator(final String opName) { String id = opName; int cnt = 1; while (graphNodeList.findGraphNode(id) != null) { ++cnt; id = opName + '(' + cnt + ')'; } final GraphNode newGraphNode = createNewGraphNode(graph, opName, id); notifyGraphEvent(new GraphEvent(events.ADD_EVENT, newGraphNode)); return newGraphNode; } static GraphNode createNewGraphNode(final Graph graph, final GraphNodeList graphNodeList, final String opName, final String id) { final Node newNode = new Node(id, opName); final XppDomElement parameters = new XppDomElement("parameters"); newNode.setConfiguration(parameters); graph.addNode(newNode); final GraphNode newGraphNode = new GraphNode(newNode); graphNodeList.add(newGraphNode); newGraphNode.setOperatorUI(OperatorUIRegistry.CreateOperatorUI(newGraphNode.getOperatorName())); return newGraphNode; } private GraphNode createNewGraphNode(final Graph graph, final String opName, final String id) { final Node newNode = new Node(id, opName); final XppDomElement parameters = new XppDomElement("parameters"); newNode.setConfiguration(parameters); graph.addNode(newNode); final GraphNode newGraphNode = new GraphNode(newNode); graphNodeList.add(newGraphNode); newGraphNode.setOperatorUI(OperatorUIRegistry.CreateOperatorUI(newGraphNode.getOperatorName())); moveWriterToLast(graph); return newGraphNode; } private void moveWriterToLast(final Graph graph) { final String writeOperatorAlias = OperatorSpi.getOperatorAlias(WriteOp.class); final GraphNode writerNode = graphNodeList.findGraphNode(writeOperatorAlias); if (writerNode != null) { removeNode(writerNode); graphNodeList.add(writerNode); graph.addNode(writerNode.getNode()); } } public void removeOperator(final GraphNode node) { notifyGraphEvent(new GraphEvent(events.REMOVE_EVENT, node)); removeNode(node); } private void removeNode(final GraphNode node) { graphNodeList.remove(node); graph.removeNode(node.getID()); } public void autoConnectGraph() { final List<GraphNode> nodes = GetGraphNodes(); Collections.sort(nodes, new GraphNodePosComparator()); for (int i = 0; i < nodes.size() - 1; ++i) { if (!nodes.get(i).HasSources()) { nodes.get(i).connectOperatorSource(nodes.get(i + 1).getID()); } } notifyConnection(); } public void notifyConnection() { notifyGraphEvent(new GraphEvent(events.CONNECT_EVENT, graphNodeList.getGraphNodes().get(0))); } private void notifyGraphEvent(final GraphEvent event) { setChanged(); notifyObservers(event); clearChanged(); } public void setOperatorParam(final String id, final String paramName, final String value) { final Node node = graph.getNode(id); DomElement xml = node.getConfiguration().getChild(paramName); if (xml == null) { xml = new XppDomElement(paramName); node.getConfiguration().addChild(xml); } xml.setValue(value); } private void AssignAllParameters() throws GraphException { final XppDom presentationXML = new XppDom("Presentation"); // save graph description final XppDom descXML = new XppDom("Description"); descXML.setValue(graphDescription); presentationXML.addChild(descXML); graphNodeList.assignParameters(presentationXML); graph.setAppData("Presentation", presentationXML); } public boolean InitGraph() throws GraphException { if (graphNodeList.isGraphComplete()) { AssignAllParameters(); ProductSetUIHandler productSetHandler = new ProductSetUIHandler(graph, graphNodeList); SubGraphHandler subGraphHandler = new SubGraphHandler(graph, graphNodeList); try { recreateGraphContext(); graphNodeList.updateGraphNodes(graphContext); //todo recreateGraphContext(); } catch (Exception e) { e.printStackTrace(); throw new GraphException(e.getMessage()); } finally { subGraphHandler.restore(); productSetHandler.restore(); } return true; } return false; } private void recreateGraphContext() throws GraphException { if (graphContext != null) graphContext.dispose(); processor = new GraphProcessor(); graphContext = new GraphContext(graph); } public void disposeGraphContext() { graphContext.dispose(); } /** * Begins graph processing * * @param pm The ProgressMonitor */ public void executeGraph(ProgressMonitor pm) { processor.executeGraph(graphContext, pm); } public File[] getPotentialOutputFiles() { final List<File> fileList = new ArrayList<>(); final Node[] nodes = graph.getNodes(); for (Node n : nodes) { if (n.getOperatorName().equalsIgnoreCase(OperatorSpi.getOperatorAlias(WriteOp.class))) { final DomElement config = n.getConfiguration(); final DomElement fileParam = config.getChild("file"); if (fileParam != null) { final String filePath = fileParam.getValue(); if (filePath != null && !filePath.isEmpty()) { final File file = new File(filePath); fileList.add(file); } } } } return fileList.toArray(new File[fileList.size()]); } public File saveGraph() throws GraphException { String filename = "myGraph"; if (lastLoadedGraphFile != null) filename = lastLoadedGraphFile.getAbsolutePath(); final SnapFileFilter fileFilter = new SnapFileFilter("XML", "xml", "Graph"); final File filePath = Dialogs.requestFileForSave("Save Graph", false, fileFilter, ".xml", filename, null, LAST_GRAPH_PATH); if (filePath != null) writeGraph(filePath.getAbsolutePath()); return filePath; } private void writeGraph(final String filePath) throws GraphException { try (FileWriter fileWriter = new FileWriter(filePath)) { AssignAllParameters(); GraphIO.write(graph, fileWriter); } catch (Exception e) { throw new GraphException("Unable to write graph to " + filePath + '\n' + e.getMessage()); } } public String getGraphAsString() throws GraphException, IOException { final StringWriter stringWriter = new StringWriter(); try { AssignAllParameters(); GraphIO.write(graph, stringWriter); } catch (Exception e) { throw new GraphException("Unable to write graph to string" + '\n' + e.getMessage()); } finally { stringWriter.close(); } return stringWriter.toString(); } public void loadGraph(final InputStream fileStream, final File file, final boolean addUI, final boolean wait) throws GraphException { try { if (fileStream == null) return; final LoadGraphThread loadGraphThread = new LoadGraphThread(fileStream, addUI); loadGraphThread.execute(); if(wait) { loadGraphThread.get(); } lastLoadedGraphFile = file; } catch (Throwable e) { throw new GraphException("Unable to load graph " + fileStream.toString() + '\n' + e.getMessage()); } } private class LoadGraphThread extends SwingWorker<Graph, Object> { private final InputStream fileStream; private final boolean addUI; public LoadGraphThread(final InputStream fileStream, final boolean addUI) { this.fileStream = fileStream; this.addUI = addUI; } @Override protected Graph doInBackground() throws Exception { final Graph graphFromFile = GPFProcessor.readGraph(new InputStreamReader(fileStream), null); setGraph(graphFromFile, addUI); notifyGraphEvent(new GraphEvent(events.REFRESH_EVENT, null)); return graphFromFile; } } public void setGraph(final Graph graphFromFile, final boolean addUI) throws GraphException { if (graphFromFile != null) { graph = graphFromFile; graphNodeList.clear(); final XppDom presentationXML = graph.getApplicationData("Presentation"); if (presentationXML != null) { // get graph description final XppDom descXML = presentationXML.getChild("Description"); if (descXML != null && descXML.getValue() != null) { graphDescription = descXML.getValue(); } } final Node[] nodes = graph.getNodes(); for (Node n : nodes) { final GraphNode newGraphNode = new GraphNode(n); if (presentationXML != null) newGraphNode.setDisplayParameters(presentationXML); graphNodeList.add(newGraphNode); if (addUI) { OperatorUI ui = OperatorUIRegistry.CreateOperatorUI(newGraphNode.getOperatorName()); if (ui == null) { throw new GraphException("Unable to load " + newGraphNode.getOperatorName()); } newGraphNode.setOperatorUI(ui); } notifyGraphEvent(new GraphEvent(events.ADD_EVENT, newGraphNode)); } } } public String getGraphDescription() { return graphDescription; } public void setGraphDescription(final String text) { graphDescription = text; } public List<File> getProductsToOpenInDAT() { final List<File> fileList = new ArrayList<>(2); final Node[] nodes = graph.getNodes(); for (Node n : nodes) { if (n.getOperatorName().equalsIgnoreCase(OperatorSpi.getOperatorAlias(WriteOp.class))) { final DomElement config = n.getConfiguration(); final DomElement fileParam = config.getChild("file"); if (fileParam != null) { final String filePath = fileParam.getValue(); if (filePath != null && !filePath.isEmpty()) { final File file = new File(filePath); if (file.exists()) { fileList.add(file); } else { final DomElement formatParam = config.getChild("formatName"); final String format = formatParam.getValue(); final String ext = ReaderUtils.findExtensionForFormat(format); File newFile = new File(file.getAbsolutePath() + ext); if (newFile.exists()) { fileList.add(newFile); } else { final String name = FileUtils.getFilenameWithoutExtension(file); newFile = new File(name + ext); if (newFile.exists()) fileList.add(newFile); } } } } } } return fileList; } /** * Update the nodes in the graph with the given reader file and writer file */ public static void setGraphIO(final GraphExecuter graphEx, final String readID, final File readPath, final String writeID, final File writePath, final String format) { final GraphNode readNode = graphEx.getGraphNodeList().findGraphNode(readID); if (readNode != null) { graphEx.setOperatorParam(readNode.getID(), "file", readPath.getAbsolutePath()); } if (writeID != null) { final GraphNode writeNode = graphEx.getGraphNodeList().findGraphNode(writeID); if (writeNode != null) { graphEx.setOperatorParam(writeNode.getID(), "formatName", format); graphEx.setOperatorParam(writeNode.getID(), "file", writePath.getAbsolutePath()); } } } public static class GraphEvent { private final events eventType; private final Object data; GraphEvent(events type, Object d) { eventType = type; data = d; } public Object getData() { return data; } public events getEventType() { return eventType; } } static class GraphNodePosComparator implements Comparator<GraphNode> { public int compare(GraphNode o1, GraphNode o2) { double x1 = o1.getPos().getX(); double y1 = o1.getPos().getY(); double x2 = o2.getPos().getX(); double y2 = o2.getPos().getY(); double h1 = FastMath.hypot(x1, y1); double h2 = FastMath.hypot(x2, y2); if (h1 > h2) return -1; else if (h1 < h2) return +1; else return 0; } } }