package application.editor; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.mxgraph.layout.mxCircleLayout; import com.mxgraph.layout.mxCompactTreeLayout; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxGeometry; import com.mxgraph.model.mxGraphModel; import com.mxgraph.model.mxIGraphModel; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxPoint; import com.mxgraph.view.mxCellState; import com.mxgraph.util.mxRectangle; import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; import algorithms.DataTransferModelAnalyzer; import algorithms.Validation; import application.layouts.*; import code.ast.CompilationUnit; import models.Edge; import models.EdgeAttribute; import models.Node; import models.algebra.Expression; import models.dataConstraintModel.Channel; import models.dataConstraintModel.ChannelMember; import models.dataConstraintModel.DataConstraintModel; import models.dataConstraintModel.ResourceHierarchy; import models.dataConstraintModel.ResourcePath; import models.dataFlowModel.DataTransferModel; import models.dataFlowModel.DataTransferChannel; import models.dataFlowModel.PushPullAttribute; import models.dataFlowModel.ChannelNode; import models.dataFlowModel.DataFlowEdge; import models.dataFlowModel.DataFlowGraph; import models.dataFlowModel.ResourceNode; import models.visualModel.FormulaChannel; import parser.Parser; import parser.Parser.TokenStream; import parser.exceptions.ExpectedAssignment; import parser.exceptions.ExpectedChannel; import parser.exceptions.ExpectedChannelName; import parser.exceptions.ExpectedEquals; import parser.exceptions.ExpectedFormulaChannel; import parser.exceptions.ExpectedGeometry; import parser.exceptions.ExpectedInOrOutOrRefOrSubKeyword; import parser.exceptions.ExpectedIoChannel; import parser.exceptions.ExpectedLeftCurlyBracket; import parser.exceptions.ExpectedModel; import parser.exceptions.ExpectedNode; import parser.exceptions.ExpectedRHSExpression; import parser.exceptions.ExpectedResource; import parser.exceptions.ExpectedRightBracket; import parser.exceptions.ExpectedStateTransition; import parser.exceptions.WrongLHSExpression; import parser.exceptions.WrongRHSExpression; import parser.exceptions.ExpectedRightCurlyBracket; import parser.exceptions.WrongPathExpression; import parser.ParserDTRAM; public class Editor { final int PORT_DIAMETER = 8; final int PORT_RADIUS = PORT_DIAMETER / 2; protected DataTransferModel model = null; protected mxGraph graph = null; private mxGraphComponent graphComponent = null; protected DataFlowGraph dataFlowGraph = null; protected String curFileName = null; protected String curFilePath = null; protected ArrayList<CompilationUnit> codes = null; private boolean bReflectingArchitectureModel = false; public Editor(mxGraphComponent graphComponent) { this.graphComponent = graphComponent; this.graph = graphComponent.getGraph(); graphComponent.setCellEditor(new DataTransferModelingCellEditor(graphComponent, this)); } public mxGraph getGraph() { return graph; } public mxGraphComponent getGraphComponent() { return this.graphComponent; } public DataTransferModel getModel() { if (model == null) { model = new DataTransferModel(); } return model; } public DataFlowGraph getDataFlowGraph() { if (dataFlowGraph == null) { analyzeDataTransferModel(getModel()); updateEdgeAttiributes(dataFlowGraph); } return dataFlowGraph; } public DataFlowGraph analyzeDataTransferModel(DataTransferModel model) { DataFlowGraph flowGraph = DataTransferModelAnalyzer.createDataFlowGraphWithStateStoringAttribute(model); dataFlowGraph = DataTransferModelAnalyzer.annotateWithSelectableDataTransferAttiribute(flowGraph); return dataFlowGraph; } public void resetDataFlowGraph() { dataFlowGraph = null; } public void setDataFlowGraph(DataFlowGraph dataFlowGraph) { this.dataFlowGraph = dataFlowGraph; } public ArrayList<CompilationUnit> getCodes() { return codes; } public void setCodes(ArrayList<CompilationUnit> codes) { this.codes = codes; } public String getCurFileName() { return curFileName; } public String getCurFilePath() { return curFilePath; } public void setCurFilePath(String curFilePath) { this.curFilePath = curFilePath; this.curFileName = new File(curFilePath).getName(); } public void clear() { model = null; ((mxGraphModel) graph.getModel()).clear(); dataFlowGraph = null; curFilePath = null; curFileName = null; codes = null; } /** * Open a given file, parse the file, construct a DataFlowModel and a mxGraph * @param file given file * @return a constructed DataFlowModel */ public DataTransferModel open(File file) { try { String extension =""; if(file != null && file.exists()) { // get a file's name String name = file.getName(); // get a file's extension extension = name.substring(name.lastIndexOf(".")); } if(extension.contains(".model")) { openModel(file); } else { ParserDTRAM parserDTRAM = new ParserDTRAM(new BufferedReader(new FileReader(file))); try { // Parse the .dtram file. model = parserDTRAM.doParseModel(); // Analyze the model. if (!Validation.checkUpdateConflict(model)) return null; DataFlowGraph dataFlowGraph = analyzeDataTransferModel(model); // Visualize the model. graph = constructGraph(model, dataFlowGraph); parserDTRAM.doParseGeometry(graph); updateEdgeAttiributes(dataFlowGraph); curFilePath = file.getAbsolutePath(); curFileName = file.getName(); return model; } catch (ExpectedChannel | ExpectedChannelName | ExpectedLeftCurlyBracket | ExpectedInOrOutOrRefOrSubKeyword | ExpectedStateTransition | ExpectedEquals | ExpectedRHSExpression | WrongLHSExpression | WrongRHSExpression | ExpectedRightBracket | ExpectedAssignment | ExpectedModel | ExpectedGeometry | ExpectedNode | ExpectedResource | ExpectedFormulaChannel | ExpectedIoChannel | ExpectedRightCurlyBracket | WrongPathExpression e) { e.printStackTrace(); } } } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } public DataTransferModel openModel(File file) { try { Parser parser = new Parser(new BufferedReader(new FileReader(file))); try { // Parse the .model file. model = parser.doParse(); // Analyze the model. if (!Validation.checkUpdateConflict(model)) return null; DataFlowGraph dataFlowGraph = analyzeDataTransferModel(model); // Visualize the model. graph = constructGraph(model, dataFlowGraph); updateEdgeAttiributes(dataFlowGraph); // Set DAG layout. // setDAGLayout(); curFilePath = file.getAbsolutePath(); curFileName = file.getName(); return model; } catch (ExpectedChannel | ExpectedChannelName | ExpectedLeftCurlyBracket | ExpectedInOrOutOrRefOrSubKeyword | ExpectedStateTransition | ExpectedEquals | ExpectedRHSExpression | WrongLHSExpression | WrongRHSExpression | ExpectedRightBracket | ExpectedAssignment | ExpectedRightCurlyBracket | WrongPathExpression e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } /**-------------------------------------------------------------------------------- * save /**-------------------------------------------------------------------------------- * */ public void save() { if (curFilePath != null) { try { File file = new File(curFilePath); String extension = ""; if(file != null && file.exists()) { // get a file's name String name = file.getName(); // get a file's extension extension = name.substring(name.lastIndexOf(".")); } if(extension.contains(".model")) { saveModel(file); } else { FileWriter filewriter = new FileWriter(file); filewriter.write(toOutputString()); filewriter.close(); } } catch (IOException e) { e.printStackTrace(); } } } public void saveModel(File file) { if (curFilePath != null) { try { FileWriter filewriter = new FileWriter(file); filewriter.write(model.getSourceText()); filewriter.close(); } catch (IOException e) { e.printStackTrace(); } } } /**-------------------------------------------------------------------------------- * get writing texts "dtram" file information is written. * * @return formatted "dtram" info texts. */ protected String toOutputString() { String fileString = ""; fileString += "model {\n"; fileString += this.model.getSourceText(); fileString += "}\n"; fileString += "geometry {\n"; Object root = graph.getDefaultParent(); for (int i = 0; i < graph.getModel().getChildCount(root); i++) { Object cell = graph.getModel().getChildAt(root, i); if (graph.getModel().isVertex(cell)) { mxGraphView view = graph.getView(); mxCellState state = view.getState(cell); int x = (int) state.getX(); int y = (int) state.getY(); int w = (int) state.getWidth(); int h = (int) state.getHeight(); for(Channel ch: model.getChannels()) { if(ch instanceof FormulaChannel && state.getLabel().equals(ch.getChannelName())) { fileString += "\tnode fc " + state.getLabel() + ":" + x + "," + y + "," + w + "," + h+"\n"; } else if(ch instanceof Channel && state.getLabel().equals(ch.getChannelName())) { fileString +="\tnode c " + state.getLabel() + ":" + x + "," + y + "," + w + "," + h+"\n"; } } for (ResourcePath res: model.getResourcePaths()){ if(res instanceof ResourcePath && state.getLabel().equals(res.getResourceName())) fileString += "\tnode r " + state.getLabel() + ":" + x + "," + y + "," + w + "," + h + "\n"; } for (Channel ioC: model.getIOChannels()) { if(ioC instanceof Channel && state.getLabel().equals(ioC.getChannelName())) { fileString += "\tnode ioc " + state.getLabel() + ":" + x + "," + y + "," + w + "," + h + "\n"; } } } } fileString += "}\n"; return fileString; } /** * Construct a mxGraph from DataFlowModel and DataFlowModel * @param model * @param dataFlowGraph * @return constructed mxGraph */ public mxGraph constructGraph(DataTransferModel model, DataFlowGraph dataFlowGraph) { bReflectingArchitectureModel = true; ((mxGraphModel) graph.getModel()).clear(); Object parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); try { mxGeometry geo1 = new mxGeometry(0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo1.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo1.setRelative(true); mxGeometry geo2 = new mxGeometry(1.0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo2.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo2.setRelative(true); Map<DataTransferChannel, Object> channelsIn = new HashMap<>(); Map<DataTransferChannel, Object> channelsOut = new HashMap<>(); Map<ResourcePath, Object> resources = new HashMap<>(); // create channel vertices for (ChannelNode c: dataFlowGraph.getRootChannelNodes()) { DataTransferChannel channel = (DataTransferChannel) c.getChannel(); if (channel.getInputResources().size() > 0) { // Normal channel if (channelsIn.get(channel) == null || channelsOut.get(channel) == null) { Object chCell = graph.insertVertex(parent, null, channel.getChannelName(), 150, 20, 30, 30); // insert a channel as a vertex mxCell port_in = new mxCell(null, geo1, "shape=ellipse;perimter=ellipsePerimeter"); port_in.setVertex(true); graph.addCell(port_in, chCell); // insert the input port of a channel mxCell port_out = new mxCell(null, geo2, "shape=ellipse;perimter=ellipsePerimeter"); port_out.setVertex(true); graph.addCell(port_out, chCell); // insert the output port of a channel channelsIn.put(channel, port_in); channelsOut.put(channel, port_out); } } else { // I/O channel if (channelsOut.get(channel) == null) { Object chCell = graph.insertVertex(parent, null, channel.getChannelName(), 150, 20, 30, 30); // insert an I/O channel as a vertex mxCell port_out = new mxCell(null, geo2, "shape=ellipse;perimter=ellipsePerimeter"); port_out.setVertex(true); graph.addCell(port_out, chCell); // insert the output port of a channel channelsOut.put((DataTransferChannel) channel, port_out); } } } // create resource vertices for (ResourceNode resNode: dataFlowGraph.getRootResourceNodes()) { int w = 300; int h = 200; ResourcePath res = resNode.getOutSideResource(); Object resource = graph.insertVertex(parent, null, res.getResourceName(), 20, 20, w, h, "shape=ellipse;perimeter=ellipsePerimeter"); // insert a resource as a vertex resources.put(res, resource); getChildResource(resource, resNode, resources, w, h); } // add input, output and reference edges for (Edge edge: dataFlowGraph.getEdges()) { DataFlowEdge dfEdge = (DataFlowEdge) edge; if (dfEdge.isChannelToResource()) { // output edge DataTransferChannel channel = ((ChannelNode) dfEdge.getSource()).getChannel(); ResourcePath dstRes = ((ResourceNode) dfEdge.getDestination()).getOutSideResource(); graph.insertEdge(parent, null, new SrcDstAttribute(channel, dstRes), channelsOut.get(channel), resources.get(dstRes), "movable=false"); } else { // input edge ResourcePath srcRes = ((ResourceNode) dfEdge.getSource()).getOutSideResource(); DataTransferChannel channel = ((ChannelNode) dfEdge.getDestination()).getChannel(); graph.insertEdge(parent, null, new SrcDstAttribute(srcRes, channel), resources.get(srcRes), channelsIn.get(channel), "movable=false"); } } for (Channel ch: model.getChannels()) { // reference edges DataTransferChannel channel = (DataTransferChannel) ch; for (ResourcePath refRes: channel.getReferenceResources()) { graph.insertEdge(parent, null, null, resources.get(refRes), channelsIn.get(channel), "dashed=true;movable=false"); } } } finally { graph.getModel().endUpdate(); } // setTreeLayout(); bReflectingArchitectureModel = false; return graph; } public void getChildResource(Object resource, ResourceNode resNode, Map<ResourcePath, Object> resources, int w, int h) { for (ResourceNode c: resNode.getChildren()) { w = w - 100; if(w <= 0) { w = 100; } h = (2 * w) / 3; ResourcePath chRes = c.getOutSideResource(); Object chResource = graph.insertVertex(resource, null, chRes.getName(), 110, 90, w, h, "shape=ellipse;perimeter=ellipsePerimeter"); // insert a resource as a vertex resources.put(chRes, chResource); getChildResource(chResource, c, resources, w, h); } } public void setDAGLayout() { Object parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); try { DAGLayout ctl = new DAGLayout(graph); ctl.execute(parent); } finally { graph.getModel().endUpdate(); } } public void updateEdgeAttiributes(DataFlowGraph dataFlowGraph) { Object parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); try { // add input, output and reference edges for (Edge e : dataFlowGraph.getEdges()) { if (e instanceof DataFlowEdge) { DataFlowEdge dataFlow = (DataFlowEdge) e; if (!dataFlow.isChannelToResource()) { ResourceNode srcRes = (ResourceNode) dataFlow.getSource(); DataTransferChannel channel = ((ChannelNode) dataFlow.getDestination()).getChannel(); // input edge for (Object edge: graph.getChildEdges(parent)) { mxCell edgeCell = (mxCell) edge; if (edgeCell.getValue() instanceof SrcDstAttribute) { SrcDstAttribute edgeAttr = (SrcDstAttribute) edgeCell.getValue(); if (srcRes.getOutSideResource().equals(edgeAttr.getSource()) && channel.equals(edgeAttr.getDestination())) { edgeCell.setValue(dataFlow.getAttribute()); break; } } } } } } } finally { graph.getModel().endUpdate(); } graph.refresh(); } public void setTreeLayout() { Object parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); try { mxCompactTreeLayout ctl = new mxCompactTreeLayout(graph); ctl.setLevelDistance(100); // ctl.setHorizontal(false); ctl.setEdgeRouting(false); ctl.execute(parent); } finally { graph.getModel().endUpdate(); } } public void setCircleLayout() { Object parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); try { mxCircleLayout ctl = new mxCircleLayout(graph); ctl.execute(parent); } finally { graph.getModel().endUpdate(); } } public ResourcePath addResourcePath(ResourcePath parentPath, String resName) { ResourcePath resourcePath = null; if (parentPath == null) { resourcePath = new ResourcePath(resName); getModel().addResourcePath(resourcePath); } else { if (resName.startsWith(Parser.LEFT_CURLY_BRACKET) && resName.endsWith(Parser.RIGHT_CURLY_BRACKET)) { TokenStream stream = new Parser.TokenStream(); Parser parser = new Parser(stream); stream.addLine(resName.substring(1, resName.length() - 1)); try { Expression exp = parser.parseTerm(stream, getModel()); resourcePath = new ResourcePath(parentPath, exp); getModel().addResourcePath(resourcePath); } catch (ExpectedRightBracket e) { e.printStackTrace(); return null; } } else { resourcePath = new ResourcePath(parentPath, resName); getModel().addResourcePath(resourcePath); } } resetDataFlowGraph(); graph.getModel().beginUpdate(); Object parent = graph.getDefaultParent(); try { graph.insertVertex(parent, null, resName, 20, 20, 80, 30, "shape=ellipse;perimeter=ellipsePerimeter"); // insert a resource as a vertex } finally { graph.getModel().endUpdate(); } return resourcePath; } public void addChannel(DataTransferChannel channelGen) { getModel().addChannel(channelGen); resetDataFlowGraph(); graph.getModel().beginUpdate(); Object parent = graph.getDefaultParent(); try { mxGeometry geo1 = new mxGeometry(0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo1.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo1.setRelative(true); mxGeometry geo2 = new mxGeometry(1.0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo2.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo2.setRelative(true); Object channel = graph.insertVertex(parent, null, channelGen.getChannelName(), 150, 20, 30, 30); // insert a channel as a vertex mxCell port_in = new mxCell(null, geo1, "shape=ellipse;perimter=ellipsePerimeter"); port_in.setVertex(true); graph.addCell(port_in, channel); // insert the input port of a channel mxCell port_out = new mxCell(null, geo2, "shape=ellipse;perimter=ellipsePerimeter"); port_out.setVertex(true); graph.addCell(port_out, channel); // insert the output port of a channel } finally { graph.getModel().endUpdate(); } } public void addIOChannel(DataTransferChannel ioChannelGen) { getModel().addIOChannel(ioChannelGen); resetDataFlowGraph(); graph.getModel().beginUpdate(); Object parent = graph.getDefaultParent(); try { mxGeometry geo2 = new mxGeometry(1.0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo2.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo2.setRelative(true); Object channel = graph.insertVertex(parent, null, ioChannelGen.getChannelName(), 150, 20, 30, 30); // insert an I/O channel as a vertex mxCell port_out = new mxCell(null, geo2, "shape=ellipse;perimter=ellipsePerimeter"); port_out.setVertex(true); graph.addCell(port_out, channel); // insert the output port of a channel } finally { graph.getModel().endUpdate(); } } public void addFormulaChannel(FormulaChannel formulaChannelGen) { getModel().addChannel(formulaChannelGen); resetDataFlowGraph(); graph.getModel().beginUpdate(); Object parent = graph.getDefaultParent(); try { mxGeometry geo1 = new mxGeometry(0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo1.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo1.setRelative(true); mxGeometry geo2 = new mxGeometry(1.0, 0.5, PORT_DIAMETER, PORT_DIAMETER); geo2.setOffset(new mxPoint(-PORT_RADIUS, -PORT_RADIUS)); geo2.setRelative(true); Object channel = graph.insertVertex(parent, null, formulaChannelGen.getChannelName(), 150, 20, 30, 30); // insert a channel as a vertex mxCell port_in = new mxCell(null, geo1, "shape=ellipse;perimter=ellipsePerimeter"); port_in.setVertex(true); graph.addCell(port_in, channel); // insert the input port of a channel mxCell port_out = new mxCell(null, geo2, "shape=ellipse;perimter=ellipsePerimeter"); port_out.setVertex(true); graph.addCell(port_out, channel); // insert the output port of a channel } finally { graph.getModel().endUpdate(); } } public boolean connectEdge(mxCell edge, mxCell src, mxCell dst) { if (bReflectingArchitectureModel) return false; DataTransferModel model = getModel(); Channel srcCh = model.getChannel((String) src.getValue()); if (srcCh == null) { srcCh = model.getIOChannel((String) src.getValue()); if (srcCh == null) { ResourcePath srcRes = model.getResourcePath((String) src.getValue()); Channel dstCh = model.getChannel((String) dst.getValue()); if (srcRes == null || dstCh == null) return false; // resource to channel edge ChannelMember srcCm = new ChannelMember(srcRes); ((DataTransferChannel ) dstCh).addChannelMemberAsInput(srcCm); edge.setValue(new SrcDstAttribute(srcRes, dstCh)); resetDataFlowGraph(); return true; } } ResourcePath dstRes = model.getResourcePath((String) dst.getValue()); if (dstRes == null) return false; // channel to resource edge ChannelMember dstCm = new ChannelMember(dstRes); ((DataTransferChannel) srcCh).addChannelMemberAsOutput(dstCm); edge.setValue(new SrcDstAttribute(srcCh, dstRes)); resetDataFlowGraph(); return true; } public void delete() { for (Object obj: graph.getSelectionCells()) { mxCell cell = (mxCell) obj; if (cell.isEdge()) { String srcName = (String) cell.getSource().getValue(); String dstName = (String) cell.getTarget().getValue(); if (model.getResourcePath(srcName) != null) { // resource to channel edge Channel ch = model.getChannel(dstName); ch.removeChannelMember(model.getResourcePath(srcName)); } else if (model.getResourcePath(dstName) != null) { // channel to resource edge Channel ch = model.getChannel(srcName); if (ch == null) { ch = model.getIOChannel(srcName); } ch.removeChannelMember(model.getResourcePath(dstName)); } } else if (cell.isVertex()) { String name = (String) cell.getValue(); if (model.getChannel(name) != null) { model.removeChannel(name); } else if (model.getIOChannel(name) != null) { model.removeIOChannel(name); } else if (model.getResourcePath(name) != null) { model.removeResourcePath(name); } } } graph.removeCells(graph.getSelectionCells()); resetDataFlowGraph(); } public void setChannelCode(DataTransferChannel ch, String code) { ch.setSourceText(code); TokenStream stream = new Parser.TokenStream(); Parser parser = new Parser(stream); for (String line: code.split("\n")) { stream.addLine(line); } try { DataTransferChannel ch2 = parser.parseChannel(getModel()); for (ChannelMember chm2: ch2.getInputChannelMembers()) { for (ChannelMember chm: ch.getInputChannelMembers()) { if (chm2.getResource() == chm.getResource()) { chm.setStateTransition(chm2.getStateTransition()); break; } } } for (ChannelMember chm2: ch2.getOutputChannelMembers()) { for (ChannelMember chm: ch.getOutputChannelMembers()) { if (chm2.getResource() == chm.getResource()) { chm.setStateTransition(chm2.getStateTransition()); break; } } } for (ChannelMember chm2: ch2.getReferenceChannelMembers()) { for (ChannelMember chm: ch.getReferenceChannelMembers()) { if (chm2.getResource() == chm.getResource()) { chm.setStateTransition(chm2.getStateTransition()); break; } } } resetDataFlowGraph(); } catch (ExpectedRightBracket | ExpectedChannel | ExpectedChannelName | ExpectedLeftCurlyBracket | ExpectedInOrOutOrRefOrSubKeyword | ExpectedStateTransition | ExpectedEquals | ExpectedRHSExpression | WrongLHSExpression | WrongRHSExpression | ExpectedAssignment | ExpectedRightCurlyBracket | WrongPathExpression e) { e.printStackTrace(); } } private class SrcDstAttribute extends EdgeAttribute { private Object src; private Object dst; public SrcDstAttribute(Object src, Object dst) { this.src = src; this.dst = dst; } public Object getSource() { return src; } public Object getDestination() { return dst; } public String toString() { return ""; } } }