1 /*
2  * Copyright (c) 2001-2006, Gaudenz Alder
3  * Copyright (c) 2005-2006, David Benson
4  *
5  * All rights reserved.
6  *
7  * This file is licensed under the JGraph software license, a copy of which
8  * will have been provided to you in the file LICENSE at the root of your
9  * installation directory. If you are unable to locate this file please
10  * contact JGraph sales for another copy.
11  */
12 package com.jgraph.example;
13 
14 import java.awt.Color;
15 import java.awt.Container;
16 import java.awt.event.ActionEvent;
17 import java.awt.geom.Point2D;
18 import java.awt.geom.Rectangle2D;
19 import java.beans.BeanInfo;
20 import java.beans.DefaultPersistenceDelegate;
21 import java.beans.Encoder;
22 import java.beans.ExceptionListener;
23 import java.beans.Expression;
24 import java.beans.IntrospectionException;
25 import java.beans.Introspector;
26 import java.beans.PersistenceDelegate;
27 import java.beans.PropertyDescriptor;
28 import java.beans.XMLDecoder;
29 import java.beans.XMLEncoder;
30 import java.io.BufferedInputStream;
31 import java.io.BufferedOutputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileNotFoundException;
35 import java.io.FileOutputStream;
36 import java.io.ObjectInputStream;
37 import java.io.ObjectOutputStream;
38 import java.net.URL;
39 import java.util.ArrayList;
40 import java.util.HashSet;
41 import java.util.Hashtable;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 
46 import javax.swing.AbstractAction;
47 import javax.swing.Action;
48 import javax.swing.ImageIcon;
49 import javax.swing.JFileChooser;
50 import javax.swing.JFrame;
51 import javax.swing.JOptionPane;
52 import javax.swing.JToolBar;
53 import javax.swing.JViewport;
54 import javax.swing.filechooser.FileFilter;
55 
56 import org.jgraph.JGraph;
57 import org.jgraph.event.GraphSelectionEvent;
58 import org.jgraph.example.GraphEd;
59 import org.jgraph.graph.AbstractCellView;
60 import org.jgraph.graph.AttributeMap;
61 import org.jgraph.graph.BasicMarqueeHandler;
62 import org.jgraph.graph.DefaultCellViewFactory;
63 import org.jgraph.graph.DefaultEdge;
64 import org.jgraph.graph.DefaultGraphCell;
65 import org.jgraph.graph.DefaultGraphModel;
66 import org.jgraph.graph.DefaultPort;
67 import org.jgraph.graph.EdgeView;
68 import org.jgraph.graph.GraphConstants;
69 import org.jgraph.graph.GraphLayoutCache;
70 import org.jgraph.graph.GraphModel;
71 import org.jgraph.graph.ParentMap;
72 import org.jgraph.graph.PortView;
73 import org.jgraph.graph.VertexView;
74 
75 /**
76  * An extension to GraphEd demonstrating more advanced JGraph features
77  */
78 public class GraphEdX extends GraphEd {
79 
80 	/**
81 	 * JGraph Factory instance for random new graphs
82 	 */
83 	protected JGraphGraphFactory graphFactory = new JGraphGraphFactory();
84 
85 	/**
86 	 * References the folding manager.
87 	 */
88 	protected JGraphFoldingManager foldingManager;
89 
90 	// Actions which Change State
91 	protected Action hide, collapse, expand, expandAll, configure;
92 
93 	/**
94 	 * File chooser for loading and saving graphs. Note that it is lazily
95 	 * instaniated, always call initFileChooser before use.
96 	 */
97 	protected JFileChooser fileChooser = null;
98 
99 	static {
100 		makeCellViewFieldsTransient(PortView.class);
101 		makeCellViewFieldsTransient(VertexView.class);
102 		makeCellViewFieldsTransient(EdgeView.class);
103 
104 		// For XML Encoding of the graph instance, we need to exclude
105 		// the marquee handler explicitely. Being an inner class of
106 		// GraphEd, it should not be part of the written file.
107 		try {
108 			BeanInfo info = Introspector.getBeanInfo(MyGraph.class);
109 			PropertyDescriptor[] propertyDescriptors = info
110 					.getPropertyDescriptors();
111 			for (int i = 0; i < propertyDescriptors.length; ++i) {
112 				PropertyDescriptor pd = propertyDescriptors[i];
113 				if (pd.getName().equals("marqueeHandler")) {
114 					pd.setValue("transient", Boolean.TRUE);
115 				}
116 			}
117 		} catch (IntrospectionException e) {
118 			e.printStackTrace();
119 		}
120 	}
121 
122 	/**
123 	 * Constructs a new application
124 	 */
GraphEdX()125 	public GraphEdX() {
126 		// Overrides the global vertex renderer
127 		VertexView.renderer = new JGraphGroupRenderer();
128 
129 		// Prepares layout actions
130 		setJMenuBar(new GraphEdXMenuBar(this, graphFactory));
131 		// Initializes actions states
132 		valueChanged(null);
133 	}
134 
135 	/**
136 	 * Show something interesting on start.
137 	 */
init()138 	public void init() {
139 		graphFactory.insertTreeSampleData(getGraph(),
140 				createCellAttributes(new Point2D.Double(0, 0)),
141 				createEdgeAttributes());
142 	}
143 
144 	// Override parent method
createGraph()145 	protected JGraph createGraph() {
146 		// Creates a model that does not allow disconnections
147 		GraphModel model = new MyGraphModel();
148 		GraphLayoutCache layoutCache = new GraphLayoutCache(model,
149 				new DefaultCellViewFactory(), true);
150 		return new MyGraph(model, layoutCache);
151 	}
152 
153 	// Override parent method
installListeners(JGraph graph)154 	protected void installListeners(JGraph graph) {
155 		super.installListeners(graph);
156 		// Adds redirector for group collapse/expand
157 		foldingManager = new JGraphFoldingManager();
158 		graph.addMouseListener(foldingManager);
159 	}
160 
uninstallListeners(JGraph graph)161 	protected void uninstallListeners(JGraph graph) {
162 		super.uninstallListeners(graph);
163 		graph.removeMouseListener(foldingManager);
164 	}
165 
166 	/**
167 	 * Updates buttons based on application state
168 	 */
valueChanged(GraphSelectionEvent e)169 	public void valueChanged(GraphSelectionEvent e) {
170 		super.valueChanged(e);
171 		// Group Button only Enabled if a cell is selected
172 		boolean enabled = !graph.isSelectionEmpty();
173 		// hide.setEnabled(enabled);
174 		expand.setEnabled(enabled);
175 		expandAll.setEnabled(enabled);
176 		collapse.setEnabled(enabled);
177 	}
178 
179 	/**
180 	 * Overrides the parent example group method to set the bounds of the
181 	 * collapsed group cell appropriately
182 	 */
group(Object[] children)183 	public void group(Object[] children) {
184 		// Order Cells by Model Layering
185 		children = graph.order(children);
186 		// If Any Cells in View
187 		if (children != null && children.length > 0) {
188 			double gs2 = 2 * graph.getGridSize();
189 			Rectangle2D collapsedBounds = graph.getCellBounds(children);
190 			collapsedBounds.setFrame(collapsedBounds.getX(), collapsedBounds
191 					.getY(), Math.max(collapsedBounds.getWidth() / 4, gs2),
192 					Math.max(collapsedBounds.getHeight() / 2, gs2));
193 			graph.snap(collapsedBounds);
194 			DefaultGraphCell group = createGroupCell(collapsedBounds);
195 			if (group != null && children != null && children.length > 0) {
196 				// Create the group structure
197 				ParentMap pm = new ParentMap();
198 				for (int i = 0; i < children.length; i++) {
199 					pm.addEntry(children[i], group);
200 				}
201 				graph.getGraphLayoutCache().insert(new Object[] { group },
202 						null, null, pm);
203 			}
204 		}
205 	}
206 
207 	/**
208 	 * Hook from GraphEd to create a new group cell
209 	 */
createGroupCell(Rectangle2D collapsedBounds)210 	protected DefaultGraphCell createGroupCell(Rectangle2D collapsedBounds) {
211 		DefaultGraphCell group = super.createGroupCell();
212 		group.addPort();
213 		GraphConstants.setInset(group.getAttributes(), 10);
214 		GraphConstants.setBackground(group.getAttributes(), new Color(240, 240,
215 				255));
216 		GraphConstants.setBorderColor(group.getAttributes(), Color.black);
217 		GraphConstants.setOpaque(group.getAttributes(), true);
218 		GraphConstants.setBorder(group.getAttributes(), JGraphShadowBorder
219 				.getSharedInstance());
220 		GraphConstants.setBounds(group.getAttributes(), collapsedBounds);
221 		return group;
222 	}
223 
224 	/**
225 	 * Hook from GraphEd to set attributes of a new cell
226 	 */
createCellAttributes(Point2D point)227 	public Map createCellAttributes(Point2D point) {
228 		Map map = super.createCellAttributes(point);
229 		GraphConstants.setInset(map, 5);
230 		GraphConstants.setGradientColor(map, new Color(200, 200, 255));
231 		return map;
232 	}
233 
234 	/**
235 	 * Hook from GraphEd to set attributes of a new edge
236 	 */
createEdgeAttributes()237 	public Map createEdgeAttributes() {
238 		Map map = super.createEdgeAttributes();
239 		// Adds a parallel edge router
240 		GraphConstants.setLineStyle(map, GraphConstants.STYLE_SPLINE);
241 		if (GraphConstants.DEFAULTFONT != null) {
242 			GraphConstants.setFont(map, GraphConstants.DEFAULTFONT
243 					.deriveFont(10f));
244 		}
245 		return map;
246 	}
247 
248 	/**
249 	 * Hook from GraphEd to add action button to the tool bar
250 	 */
createToolBar()251 	public JToolBar createToolBar() {
252 		JToolBar toolbar = super.createToolBar();
253 
254 		// Collapse
255 		collapse = new AbstractAction() {
256 			public void actionPerformed(ActionEvent e) {
257 				graph.getGraphLayoutCache().setVisible(graph.getSelectionCells(), false);
258 			}
259 		};
260 		URL url = getClass().getClassLoader().getResource(
261 				"org/jgraph/example/resources/collapse.gif");
262 		collapse.putValue(Action.SMALL_ICON, new ImageIcon(url));
263 		collapse.setEnabled(false);
264 		toolbar.add(collapse);
265 
266 		// Expand
267 		expand = new AbstractAction() {
268 			public void actionPerformed(ActionEvent e) {
269 				graph.getGraphLayoutCache().expand(graph.getSelectionCells());
270 			}
271 		};
272 		url = getClass().getClassLoader().getResource(
273 				"org/jgraph/example/resources/expand.gif");
274 		expand.putValue(Action.SMALL_ICON, new ImageIcon(url));
275 		expand.setEnabled(false);
276 		toolbar.add(expand);
277 
278 		// ExpandAll
279 		expandAll = new AbstractAction() {
280 			public void actionPerformed(ActionEvent e) {
281 				Object[] allCells = graph.getDescendants(graph.getRoots());
282 				Map nested = new Hashtable();
283 				for (int i=0; i < allCells.length; i++) {
284 					Map attributeMap = new Hashtable();
285 					GraphConstants.setForeground(attributeMap, Color.BLACK);
286 					nested.put(allCells[i], attributeMap);
287 				}
288 				graph.getModel().edit(nested, null, null, null);
289 			}
290 		};
291 		url = getClass().getClassLoader().getResource(
292 				"org/jgraph/example/resources/expandAll.gif");
293 		expandAll.putValue(Action.SMALL_ICON, new ImageIcon(url));
294 		expandAll.setEnabled(false);
295 		toolbar.add(expandAll);
296 		return toolbar;
297 	}
298 
serializeGraph()299 	public void serializeGraph() {
300 		int returnValue = JFileChooser.CANCEL_OPTION;
301 		initFileChooser();
302 		returnValue = fileChooser.showSaveDialog(graph);
303 		if (returnValue == JFileChooser.APPROVE_OPTION) {
304 			Container parent = graph.getParent();
305 			BasicMarqueeHandler marquee = graph.getMarqueeHandler();
306 			graph.setMarqueeHandler(null);
307 			try {
308 				// Serializes the graph by removing it from the component
309 				// hierarchy and removing all listeners from it. The marquee
310 				// handler, begin an inner class of GraphEd, is not marked
311 				// serializable and will therefore not be stored. This must
312 				// be taken into account when deserializing a graph.
313 				uninstallListeners(graph);
314 				parent.remove(graph);
315 				ObjectOutputStream out = new ObjectOutputStream(
316 						new BufferedOutputStream(new FileOutputStream(
317 								fileChooser.getSelectedFile())));
318 				out.writeObject(graph);
319 				out.flush();
320 				out.close();
321 			} catch (Exception e) {
322 				e.printStackTrace();
323 				JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
324 						JOptionPane.ERROR_MESSAGE);
325 			} finally {
326 				// Adds the component back into the component hierarchy
327 				graph.setMarqueeHandler(marquee);
328 				if (parent instanceof JViewport) {
329 					JViewport viewPort = (JViewport) parent;
330 					viewPort.setView(graph);
331 				} else {
332 					// Best effort...
333 					parent.add(graph);
334 				}
335 				// And reinstalls the listener
336 				installListeners(graph);
337 			}
338 		}
339 	}
340 
deserializeGraph()341 	public void deserializeGraph() {
342 		int returnValue = JFileChooser.CANCEL_OPTION;
343 		initFileChooser();
344 		returnValue = fileChooser.showOpenDialog(graph);
345 		if (returnValue == JFileChooser.APPROVE_OPTION) {
346 			Container parent = graph.getParent();
347 			BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler();
348 			try {
349 				uninstallListeners(graph);
350 				parent.remove(graph);
351 				ObjectInputStream in = new ObjectInputStream(
352 						new BufferedInputStream(new FileInputStream(fileChooser
353 								.getSelectedFile())));
354 				graph = (JGraph) in.readObject();
355 				// Take the marquee handler from the original graph and
356 				// use it in the new graph as well.
357 				graph.setMarqueeHandler(marqueeHandler);
358 				// Adds the component back into the component hierarchy
359 				if (parent instanceof JViewport) {
360 					JViewport viewPort = (JViewport) parent;
361 					viewPort.setView(graph);
362 				} else {
363 					// Best effort...
364 					parent.add(graph);
365 				}
366 				// graph.setMarqueeHandler(previousHandler);
367 				// And reinstalls the listener
368 				installListeners(graph);
369 			} catch (Exception e) {
370 				e.printStackTrace();
371 				JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
372 						JOptionPane.ERROR_MESSAGE);
373 			}
374 		}
375 	}
376 
saveFile()377 	public void saveFile() {
378 		int returnValue = JFileChooser.CANCEL_OPTION;
379 		initFileChooser();
380 		returnValue = fileChooser.showSaveDialog(graph);
381 		if (returnValue == JFileChooser.APPROVE_OPTION) {
382 			XMLEncoder encoder;
383 			Container parent = graph.getParent();
384 			try {
385 				uninstallListeners(graph);
386 				parent.remove(graph);
387 				encoder = new XMLEncoder(new BufferedOutputStream(
388 						new FileOutputStream(fileChooser.getSelectedFile())));
389 				configureEncoder(encoder);
390 				encoder.writeObject(graph);
391 				encoder.close();
392 			} catch (Exception e) {
393 				JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
394 						JOptionPane.ERROR_MESSAGE);
395 			} finally {
396 				// Adds the component back into the component hierarchy
397 				if (parent instanceof JViewport) {
398 					JViewport viewPort = (JViewport) parent;
399 					viewPort.setView(graph);
400 				} else {
401 					// Best effort...
402 					parent.add(graph);
403 				}
404 				// And reinstalls the listener
405 				installListeners(graph);
406 			}
407 		}
408 	}
409 
openFile()410 	public void openFile() {
411 		int returnValue = JFileChooser.CANCEL_OPTION;
412 		initFileChooser();
413 		returnValue = fileChooser.showOpenDialog(graph);
414 		if (returnValue == JFileChooser.APPROVE_OPTION) {
415 			Container parent = graph.getParent();
416 			BasicMarqueeHandler marqueeHandler = graph.getMarqueeHandler();
417 			try {
418 				uninstallListeners(graph);
419 				parent.remove(graph);
420 				XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(
421 						new FileInputStream(fileChooser.getSelectedFile())));
422 
423 				// In case you need to debug XML decoding here is how
424 				// to print a stack trace
425 				/*
426 				decoder.setExceptionListener(new ExceptionListener()
427 				{
428 					public void exceptionThrown(Exception exception)
429 					{
430 						exception.printStackTrace();
431 					}
432 				});
433 				*/
434 
435 				graph = (JGraph) decoder.readObject();
436 				// Take the marquee handler from the original graph and
437 				// use it in the new graph as well.
438 				graph.setMarqueeHandler(marqueeHandler);
439 				// Adds the component back into the component hierarchy
440 				if (parent instanceof JViewport) {
441 					JViewport viewPort = (JViewport) parent;
442 					viewPort.setView(graph);
443 				} else {
444 					// Best effort...
445 					parent.add(graph);
446 				}
447 				// graph.setMarqueeHandler(previousHandler);
448 				// And reinstalls the listener
449 				installListeners(graph);
450 			} catch (FileNotFoundException e) {
451 				JOptionPane.showMessageDialog(graph, e.getMessage(), "Error",
452 						JOptionPane.ERROR_MESSAGE);
453 			}
454 
455 		}
456 	}
457 
458 	/**
459 	 * Utility method that ensures the file chooser is created. Start-up time
460 	 * is improved by lazily instaniating choosers.
461 	 *
462 	 */
initFileChooser()463 	protected void initFileChooser() {
464 		if (fileChooser == null) {
465 			fileChooser = new JFileChooser();
466 			FileFilter fileFilter = new FileFilter() {
467 				/**
468 				 * @see javax.swing.filechooser.FileFilter#accept(File)
469 				 */
470 				public boolean accept(File f) {
471 					if (f == null)
472 						return false;
473 					if (f.getName() == null)
474 						return false;
475 					if (f.getName().endsWith(".xml"))
476 						return true;
477 					if (f.getName().endsWith(".ser"))
478 						return true;
479 					if (f.isDirectory())
480 						return true;
481 
482 					return false;
483 				}
484 
485 				/**
486 				 * @see javax.swing.filechooser.FileFilter#getDescription()
487 				 */
488 				public String getDescription() {
489 					return "GraphEd file (.xml, .ser)";
490 				}
491 			};
492 			fileChooser.setFileFilter(fileFilter);
493 		}
494 	}
495 
configureEncoder(XMLEncoder encoder)496 	protected void configureEncoder(XMLEncoder encoder) {
497 		// Better debugging output, in case you need it
498 		encoder.setExceptionListener(new ExceptionListener() {
499 			public void exceptionThrown(Exception e) {
500 				e.printStackTrace();
501 			}
502 		});
503 
504 		encoder.setPersistenceDelegate(DefaultGraphModel.class,
505 				new DefaultPersistenceDelegate(new String[] { "roots",
506 						"attributes" }));
507 		encoder.setPersistenceDelegate(MyGraphModel.class,
508 				new DefaultPersistenceDelegate(new String[] { "roots",
509 						"attributes" }));
510 
511 		// Note: In the static initializer the marquee handler of the
512 		// MyGraph class is made transient to avoid being written out.
513 		encoder.setPersistenceDelegate(MyGraph.class,
514 				new DefaultPersistenceDelegate(new String[] { "model",
515 						"graphLayoutCache" }));
516 		encoder
517 				.setPersistenceDelegate(GraphLayoutCache.class,
518 						new DefaultPersistenceDelegate(new String[] { "model",
519 								"factory", "cellViews", "hiddenCellViews",
520 								"partial" }));
521 		encoder.setPersistenceDelegate(DefaultGraphCell.class,
522 				new DefaultPersistenceDelegate(new String[] { "userObject" }));
523 		encoder.setPersistenceDelegate(DefaultEdge.class,
524 				new DefaultPersistenceDelegate(new String[] { "userObject" }));
525 		encoder.setPersistenceDelegate(DefaultPort.class,
526 				new DefaultPersistenceDelegate(new String[] { "userObject" }));
527 		encoder.setPersistenceDelegate(AbstractCellView.class,
528 				new DefaultPersistenceDelegate(new String[] { "cell",
529 						"attributes" }));
530 		encoder.setPersistenceDelegate(DefaultEdge.DefaultRouting.class,
531 				new PersistenceDelegate() {
532 					protected Expression instantiate(Object oldInstance,
533 							Encoder out) {
534 						return new Expression(oldInstance,
535 								GraphConstants.class, "getROUTING_SIMPLE", null);
536 					}
537 				});
538 		encoder.setPersistenceDelegate(DefaultEdge.LoopRouting.class,
539 				new PersistenceDelegate() {
540 					protected Expression instantiate(Object oldInstance,
541 							Encoder out) {
542 						return new Expression(oldInstance,
543 								GraphConstants.class, "getROUTING_DEFAULT",
544 								null);
545 					}
546 				});
547 		encoder.setPersistenceDelegate(JGraphShadowBorder.class,
548 				new PersistenceDelegate() {
549 					protected Expression instantiate(Object oldInstance,
550 							Encoder out) {
551 						return new Expression(oldInstance,
552 								JGraphShadowBorder.class, "getSharedInstance",
553 								null);
554 					}
555 				});
556 		encoder.setPersistenceDelegate(ArrayList.class, encoder
557 				.getPersistenceDelegate(List.class));
558 	}
559 
560 	/**
561 	 * Main method
562 	 */
main(String[] args)563 	public static void main(String[] args) {
564 		try {
565 			// Switch off D3D because of Sun XOR painting bug
566 			// See http://www.jgraph.com/forum/viewtopic.php?t=4066
567 			System.setProperty("sun.java2d.d3d", "false");
568 			// Construct Frame
569 			JFrame frame = new JFrame("GraphEdX");
570 			// Set Close Operation to Exit
571 			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
572 			// Fetch URL to Icon Resource
573 			URL jgraphUrl = GraphEdX.class.getClassLoader().getResource(
574 					"org/jgraph/example/resources/jgraph.gif");
575 			// If Valid URL
576 			if (jgraphUrl != null) {
577 				// Load Icon
578 				ImageIcon jgraphIcon = new ImageIcon(jgraphUrl);
579 				// Use in Window
580 				frame.setIconImage(jgraphIcon.getImage());
581 			}
582 			// Add an Editor Panel
583 			GraphEdX app = new GraphEdX();
584 			frame.getContentPane().add(app);
585 			app.init();
586 			// Set Default Size
587 			frame.setSize(640, 480);
588 			// Show Frame
589 			frame.setVisible(true);
590 		} catch (Exception e) {
591 			e.printStackTrace();
592 		}
593 	}
594 
595 	/**
596 	 * Makes all fields but <code>cell</code> and <code>attributes</code>
597 	 * transient in the bean info of <code>clazz</code>.
598 	 *
599 	 * @param clazz
600 	 *            The cell view class who fields should be made transient.
601 	 */
makeCellViewFieldsTransient(Class clazz)602 	public static void makeCellViewFieldsTransient(Class clazz) {
603 		try {
604 			BeanInfo info = Introspector.getBeanInfo(clazz);
605 			PropertyDescriptor[] propertyDescriptors = info
606 					.getPropertyDescriptors();
607 			for (int i = 0; i < propertyDescriptors.length; ++i) {
608 				PropertyDescriptor pd = propertyDescriptors[i];
609 				if (!pd.getName().equals("cell")
610 						&& !pd.getName().equals("attributes")) {
611 					pd.setValue("transient", Boolean.TRUE);
612 				}
613 			}
614 		} catch (IntrospectionException e) {
615 			e.printStackTrace();
616 		}
617 	}
618 
619 	/**
620 	 * Encodable graph model with related constructor. Note: This class must be
621 	 * static for the XML encoding to work.
622 	 */
623 	public static class MyGraphModel extends DefaultGraphModel {
624 
MyGraphModel()625 		public MyGraphModel() {
626 			super();
627 		}
628 
MyGraphModel(List roots, AttributeMap attributes)629 		public MyGraphModel(List roots, AttributeMap attributes) {
630 			super(roots, attributes);
631 		}
632 
acceptsSource(Object edge, Object port)633 		public boolean acceptsSource(Object edge, Object port) {
634 			return (port != null);
635 		}
636 
acceptsTarget(Object edge, Object port)637 		public boolean acceptsTarget(Object edge, Object port) {
638 			return (port != null);
639 		}
640 	}
641 
642 }
643