Xcos GUI: split creation now works
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / graph / XcosDiagram.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2009 - DIGITEO - Bruno JOFRET
4  * Copyright (C) 2009-2010 - DIGITEO - Clement DAVID
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
11  *
12  */
13
14 package org.scilab.modules.xcos.graph;
15
16 import java.awt.GraphicsEnvironment;
17 import java.awt.event.ActionEvent;
18 import java.awt.event.ActionListener;
19 import java.beans.PropertyChangeEvent;
20 import java.beans.PropertyChangeListener;
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashSet;
29 import java.util.Hashtable;
30 import java.util.IllegalFormatException;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Queue;
35 import java.util.Set;
36 import java.util.logging.Logger;
37
38 import javax.swing.JFileChooser;
39 import javax.swing.JOptionPane;
40 import javax.swing.SwingWorker;
41 import javax.swing.Timer;
42
43 import org.scilab.modules.graph.ScilabGraph;
44 import org.scilab.modules.graph.utils.ScilabGraphConstants;
45 import org.scilab.modules.gui.bridge.filechooser.SwingScilabFileChooser;
46 import org.scilab.modules.gui.bridge.tab.SwingScilabDockablePanel;
47 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
48 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
49 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
50 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
51 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
52 import org.scilab.modules.xcos.JavaController;
53 import org.scilab.modules.xcos.Kind;
54 import org.scilab.modules.xcos.ObjectProperties;
55 import org.scilab.modules.xcos.VectorOfDouble;
56 import org.scilab.modules.xcos.VectorOfInt;
57 import org.scilab.modules.xcos.Xcos;
58 import org.scilab.modules.xcos.XcosTab;
59 import org.scilab.modules.xcos.actions.SaveAsAction;
60 import org.scilab.modules.xcos.block.AfficheBlock;
61 import org.scilab.modules.xcos.block.BasicBlock;
62 import org.scilab.modules.xcos.block.SplitBlock;
63 import org.scilab.modules.xcos.block.TextBlock;
64 import org.scilab.modules.xcos.block.io.EventInBlock;
65 import org.scilab.modules.xcos.block.io.EventOutBlock;
66 import org.scilab.modules.xcos.block.io.ExplicitInBlock;
67 import org.scilab.modules.xcos.block.io.ExplicitOutBlock;
68 import org.scilab.modules.xcos.block.io.ImplicitInBlock;
69 import org.scilab.modules.xcos.block.io.ImplicitOutBlock;
70 import org.scilab.modules.xcos.configuration.ConfigurationManager;
71 import org.scilab.modules.xcos.graph.model.XcosCell;
72 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
73 import org.scilab.modules.xcos.graph.model.XcosCellFactory.BlockInterFunction;
74 import org.scilab.modules.xcos.graph.swing.GraphComponent;
75 import org.scilab.modules.xcos.io.XcosFileType;
76 import org.scilab.modules.xcos.link.BasicLink;
77 import org.scilab.modules.xcos.link.commandcontrol.CommandControlLink;
78 import org.scilab.modules.xcos.link.explicit.ExplicitLink;
79 import org.scilab.modules.xcos.link.implicit.ImplicitLink;
80 import org.scilab.modules.xcos.port.BasicPort;
81 import org.scilab.modules.xcos.port.BasicPort.Type;
82 import org.scilab.modules.xcos.port.Orientation;
83 import org.scilab.modules.xcos.port.PortCheck;
84 import org.scilab.modules.xcos.port.command.CommandPort;
85 import org.scilab.modules.xcos.port.control.ControlPort;
86 import org.scilab.modules.xcos.port.input.ExplicitInputPort;
87 import org.scilab.modules.xcos.port.input.ImplicitInputPort;
88 import org.scilab.modules.xcos.port.output.ExplicitOutputPort;
89 import org.scilab.modules.xcos.port.output.ImplicitOutputPort;
90 import org.scilab.modules.xcos.preferences.XcosOptions;
91 import org.scilab.modules.xcos.utils.BlockPositioning;
92 import org.scilab.modules.xcos.utils.XcosConstants;
93 import org.scilab.modules.xcos.utils.XcosDialogs;
94 import org.scilab.modules.xcos.utils.XcosMessages;
95
96 import com.mxgraph.model.mxCell;
97 import com.mxgraph.model.mxGeometry;
98 import com.mxgraph.model.mxGraphModel;
99 import com.mxgraph.model.mxICell;
100 import com.mxgraph.util.mxEvent;
101 import com.mxgraph.util.mxEventObject;
102 import com.mxgraph.util.mxPoint;
103 import com.mxgraph.util.mxRectangle;
104 import com.mxgraph.util.mxUndoableEdit;
105 import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
106 import com.mxgraph.view.mxGraphSelectionModel;
107 import com.mxgraph.view.mxMultiplicity;
108
109 /**
110  * The base class for a diagram. This class contains jgraphx + Scicos data.
111  */
112 public class XcosDiagram extends ScilabGraph {
113     private static final Logger LOG = Logger.getLogger(XcosDiagram.class.getName());
114
115     private static final String MODIFIED = "modified";
116     private static final String CELLS = "cells";
117     public static final String IN = "in";
118     public static final String OUT = "out";
119     public static final String EIN = "ein";
120     public static final String EOUT = "eout";
121
122     /**
123      * Prefix used to tag text node.
124      */
125     public static final String HASH_IDENTIFIER = "#identifier";
126
127     /**
128      * Default geometry used while adding a label to a block (on the middle and
129      * below the bottom of the parent block)
130      */
131     private static final mxGeometry DEFAULT_LABEL_GEOMETRY = new mxGeometry(0.5, 1.1, 0.0, 0.0);
132
133     /*
134      * diagram data
135      */
136
137     // the associated parameters
138     private final ScicosParameters scicosParameters;
139
140     // the associated object uid
141     private final long uid;
142     // the kind of the diagram : can be either a DIAGRAM or a BLOCK (eg. for superblocks)
143     private final Kind kind;
144
145     /**
146      * Constructor
147      *
148      * @param withVisibleFeatures true if the visible features should be activated, false otherwise. Disable it on encode/decode leads to a huge performance gain.
149      */
150     public XcosDiagram(final long diagramId, final Kind kind) {
151         super(new mxGraphModel(), Xcos.getInstance().getStyleSheet());
152
153         this.uid = diagramId;
154         this.kind = kind;
155         new JavaController().referenceObject(uid);
156
157         // Scicos related setup
158         if (kind == Kind.DIAGRAM) {
159             scicosParameters = new ScicosParameters(diagramId);
160         } else {
161             scicosParameters = null;
162         }
163
164         // add the default parent (the JGraphX layer)
165         XcosCell parent = new XcosCell(this.uid, this.kind);
166         ((mxICell) getModel().getRoot()).insert(parent);
167         setDefaultParent(parent);
168
169         setComponent(new GraphComponent(this));
170         initComponent();
171
172         // Forbid disconnecting cells once it is connected.
173         setCellsDisconnectable(false);
174
175         // Forbid pending edges.
176         setAllowDanglingEdges(false);
177
178         // Cannot connect port to itself.
179         setAllowLoops(false);
180
181         // Override isCellResizable to filter what the user can resize
182         setCellsResizable(true);
183
184         /* Labels use HTML if not equal to interface function name */
185         setHtmlLabels(true);
186         /*
187          * by default every label is movable, see
188          * XcosDiagram##isLabelMovable(java.lang.Object) for restrictions
189          */
190         setVertexLabelsMovable(true);
191         setEdgeLabelsMovable(true);
192
193         //
194         setCloneInvalidEdges(true);
195
196         // Override isCellEditable to filter what the user can edit
197         setCellsEditable(true);
198
199         setConnectableEdges(true);
200
201         // Do not clear edge points on connect
202         setResetEdgesOnConnect(false);
203
204         setMultiplicities();
205
206         setAutoOrigin(true);
207     }
208
209     /*
210      * Static helpers
211      */
212
213     /**
214      * Only return the instanceof klass
215      *
216      * @param selection
217      *            the selection to filter out
218      * @param klass
219      *            the class selector
220      * @return the selection with only klass instance.
221      */
222     public static Object[] filterByClass(final Object[] selection, final Class<BasicBlock> klass) {
223         return mxGraphModel.filterCells(selection, new mxGraphModel.Filter() {
224             @Override
225             public boolean filter(Object cell) {
226                 return klass.isInstance(cell);
227             }
228         });
229     }
230
231     /**
232      * Sort the blocks per first integer parameter value
233      *
234      * @param blocks
235      *            the block list
236      * @return the sorted block list (same instance)
237      */
238     public List <? extends BasicBlock > iparSort(final List <? extends BasicBlock > blocks) {
239         Collections.sort(blocks, new Comparator<BasicBlock>() {
240             @Override
241             public int compare(BasicBlock o1, BasicBlock o2) {
242
243                 JavaController controller = new JavaController();
244
245                 final VectorOfInt data1 = new VectorOfInt();
246                 final VectorOfInt data2 = new VectorOfInt();
247
248                 controller.getObjectProperty(o1.getUID(), Kind.BLOCK, ObjectProperties.IPAR, data1);
249                 controller.getObjectProperty(o2.getUID(), Kind.BLOCK, ObjectProperties.IPAR, data2);
250
251                 final int value1;
252                 if (data1.size() >= 1) {
253                     value1 = data1.get(0);
254                 } else {
255                     value1 = 0;
256                 }
257
258                 final int value2;
259                 if (data2.size() >= 1) {
260                     value2 = data2.get(0);
261                 } else {
262                     value2 = 0;
263                 }
264
265                 return value1 - value2;
266             }
267         });
268         return blocks;
269     }
270
271     /**
272      * @param <T>
273      *            The type to work on
274      * @param klass
275      *            the class instance to work on
276      * @return list of typed block
277      */
278     @SuppressWarnings("unchecked")
279     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T> klass) {
280         final List<T> list = new ArrayList<T>();
281
282         int blockCount = getModel().getChildCount(getDefaultParent());
283
284         for (int i = 0; i < blockCount; i++) {
285             Object cell = getModel().getChildAt(getDefaultParent(), i);
286             if (klass.isInstance(cell)) {
287                 // According to the test we are sure that the cell is an
288                 // instance of T. Thus we can safely cast it.
289                 list.add((T) cell);
290             }
291         }
292         return list;
293     }
294
295     /**
296      * @param <T>
297      * @param <T>
298      *            The type to work on
299      * @param klasses
300      *            the class instance list to work on
301      * @return list of typed block
302      */
303     private <T extends BasicBlock> List<T> getAllTypedBlock(Class<T>[] klasses) {
304         final List<T> list = new ArrayList<T>();
305         for (Class<T> klass : klasses) {
306             list.addAll(getAllTypedBlock(klass));
307         }
308         return list;
309     }
310
311     /**
312      * Fill the context with I/O port
313      *
314      * @param context
315      *            the context to fill
316      */
317     @SuppressWarnings("unchecked")
318     protected void fillContext(final Hashtable<Object, Object> context) {
319         if (!context.containsKey(IN)) {
320             context.put(IN, iparSort(getAllTypedBlock( new Class [] { ExplicitInBlock.class, ImplicitInBlock.class })));
321         }
322         if (!context.containsKey(OUT)) {
323             context.put(OUT, iparSort(getAllTypedBlock(new Class[] { ExplicitOutBlock.class, ImplicitOutBlock.class })));
324         }
325         if (!context.containsKey(EIN)) {
326             context.put(EIN, iparSort(getAllTypedBlock(new Class[] { EventInBlock.class })));
327         }
328         if (!context.containsKey(EOUT)) {
329             context.put(EOUT, iparSort(getAllTypedBlock(new Class[] { EventOutBlock.class })));
330         }
331     }
332
333     @Override
334     public String validateCell(final Object cell, final Hashtable<Object, Object> context) {
335         if (kind == Kind.BLOCK) {
336             return validateChildDiagram(cell, context);
337         } else {
338             // does not perform any validation on a root diagram
339             return null;
340         }
341     }
342
343     /**
344      * Validate I/O ports.
345      *
346      * /!\ No model modification should be made in this method, this is only a validation method.
347      *
348      * @param cell
349      *            Cell that represents the cell to validate.
350      * @param context
351      *            Hashtable that represents the global validation state.
352      */
353     public String validateChildDiagram(final Object cell, final Hashtable<Object, Object> context) {
354         String err = null;
355
356         /*
357          * Only validate I/O blocks
358          */
359
360         // get the key
361         final String key;
362         if (cell instanceof ExplicitInBlock || cell instanceof ImplicitInBlock) {
363             key = IN;
364         } else if (cell instanceof ExplicitOutBlock || cell instanceof ImplicitOutBlock) {
365             key = OUT;
366         } else if (cell instanceof EventInBlock) {
367             key = EIN;
368         } else if (cell instanceof EventOutBlock) {
369             key = EOUT;
370         } else {
371             return null;
372         }
373         final BasicBlock block = (BasicBlock) cell;
374
375         /*
376          * Prepare validation
377          */
378
379         // fill the context once
380         fillContext(context);
381
382         /*
383          * Validate with ipar
384          */
385
386         // get the real index
387         final List <? extends BasicBlock > blocks = (List <? extends BasicBlock > ) context.get(key);
388         final int realIndex = blocks.indexOf(block) + 1;
389
390         // get the user index
391         JavaController controller = new JavaController();
392         VectorOfInt ipar = new VectorOfInt();
393         controller.getObjectProperty(realIndex, Kind.BLOCK, ObjectProperties.IPAR, ipar);
394         if (ipar.size() > 1) {
395             return err;
396         }
397         final int userIndex = ipar.get(0);
398
399         // if the indexes are not equals, alert the user.
400         if (realIndex != userIndex) {
401             final StringBuilder str = new StringBuilder();
402             str.append("<html><body><em>");
403             str.append(XcosMessages.WRONG_PORT_NUMBER);
404             str.append("</em><br/>");
405             str.append(String.format(XcosMessages.EXPECTING_NUMBER, realIndex));
406             str.append("</body></html>    ");
407
408             err = str.toString();
409         }
410
411         return err;
412     }
413
414
415
416
417     /*
418      * Static diagram listeners
419      */
420
421     /**
422      * CellResizedTracker Called when mxEvents.CELLS_RESIZED is fired.
423      */
424     private static final class CellResizedTracker implements mxIEventListener {
425
426         private static CellResizedTracker instance;
427
428         /**
429          * Constructor
430          */
431         private CellResizedTracker() {
432         }
433
434         /**
435          * @return the instance
436          */
437         public static CellResizedTracker getInstance() {
438             if (instance == null) {
439                 instance = new CellResizedTracker();
440             }
441             return instance;
442         }
443
444         /**
445          * Update the cell view
446          *
447          * @param source
448          *            the source instance
449          * @param evt
450          *            the event data
451          * @see com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
452          *      com.mxgraph.util.mxEventObject)
453          */
454         @Override
455         public void invoke(final Object source, final mxEventObject evt) {
456             final XcosDiagram diagram = (XcosDiagram) source;
457             final Object[] cells = (Object[]) evt.getProperty(CELLS);
458
459             diagram.getModel().beginUpdate();
460             try {
461                 for (int i = 0; i < cells.length; ++i) {
462                     if (cells[i] instanceof BasicBlock) {
463                         BlockPositioning.updateBlockView(diagram, (BasicBlock) cells[i]);
464                     }
465                 }
466             } finally {
467                 diagram.getModel().endUpdate();
468             }
469         }
470     }
471
472     /**
473      * Update the modified block on undo/redo
474      */
475     private static final class UndoUpdateTracker implements mxIEventListener {
476         private static UndoUpdateTracker instance;
477
478         /**
479          * Constructor
480          */
481         public UndoUpdateTracker() {
482         }
483
484         /**
485          * @return the instance
486          */
487         public static UndoUpdateTracker getInstance() {
488             if (instance == null) {
489                 instance = new UndoUpdateTracker();
490             }
491             return instance;
492         }
493
494         /**
495          * Update the block and style on undo
496          *
497          * @param source
498          *            the source instance
499          * @param evt
500          *            the event data
501          * @see com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
502          *      com.mxgraph.util.mxEventObject)
503          */
504         @Override
505         public void invoke(final Object source, final mxEventObject evt) {
506             final mxUndoableEdit edit = (mxUndoableEdit) evt.getProperty(ScilabGraphConstants.EVENT_CHANGE_EDIT);
507
508             final mxGraphModel model = (mxGraphModel) edit.getSource();
509             final List<mxUndoableChange> changes = edit.getChanges();
510
511             final Object[] changedCells = getSelectionCellsForChanges(changes, model);
512             model.beginUpdate();
513             try {
514                 // FIXME manage that for the MVC
515                 //                for (final Object object : changedCells) {
516                 //                    if (object instanceof BasicBlock) {
517                 //                        final BasicBlock current = (BasicBlock) object;
518                 //                        final XcosDiagram graph = current.getParentDiagram();
519                 //
520                 //                        // When we change the style property we have to update
521                 //                        // some BasiBlock fields
522                 //                        if (changes.get(0) instanceof mxStyleChange) {
523                 //                            current.updateFieldsFromStyle();
524                 //                        }
525                 //
526                 //                        // update the superblock container ports if the block is
527                 //                        // inside a superblock diagram
528                 //                        if (graph instanceof SuperBlockDiagram) {
529                 //                            SuperBlockDiagram superdiagram = (SuperBlockDiagram) current.getParentDiagram();
530                 //                            SuperBlock superblock = superdiagram.getContainer();
531                 //                            superblock.updateExportedPort();
532                 //                        }
533                 //
534                 //                        // Update the block position
535                 //                        BlockPositioning.updateBlockView(current);
536                 //
537                 //                        // force a refresh of the block ports and links
538                 //                        // connected to these ports
539                 //                        final int childCount = model.getChildCount(current);
540                 //                        for (int i = 0; i < childCount; i++) {
541                 //                            final Object port = model.getChildAt(current, i);
542                 //                            graph.getView().clear(port, true, true);
543                 //                            final int edgeCount = model.getEdgeCount(port);
544                 //                            for (int j = 0; j < edgeCount; j++) {
545                 //                                final Object edge = model.getEdgeAt(port, j);
546                 //                                graph.getView().clear(edge, true, true);
547                 //                            }
548                 //                        }
549                 //                        // force a refresh of the block
550                 //                        graph.getView().clear(current, true, true);
551                 //
552                 //                        graph.getView().validate();
553                 //                        graph.repaint();
554                 //                    }
555                 //                }
556             } finally {
557                 model.endUpdate();
558             }
559         }
560     }
561
562     /**
563      * Refresh each block on modification (update port position, etc...)
564      */
565     private static final class RefreshBlockTracker implements mxIEventListener {
566         private static RefreshBlockTracker instance;
567
568         /**
569          * Default constructor
570          */
571         private RefreshBlockTracker() {
572         }
573
574         /**
575          * @return the instance
576          */
577         public static RefreshBlockTracker getInstance() {
578             if (instance == null) {
579                 instance = new RefreshBlockTracker();
580             }
581             return instance;
582         }
583
584         /**
585          * Refresh the block on port added
586          *
587          * @param sender
588          *            the diagram
589          * @param evt
590          *            the event
591          * @see com.mxgraph.util.mxEventSource.mxIEventListener#invoke(java.lang.Object,
592          *      com.mxgraph.util.mxEventObject)
593          */
594         @Override
595         public void invoke(Object sender, mxEventObject evt) {
596             final XcosDiagram diagram = (XcosDiagram) sender;
597
598             diagram.getModel().beginUpdate();
599             try {
600                 final BasicBlock updatedBlock = (BasicBlock) evt.getProperty(XcosConstants.EVENT_BLOCK_UPDATED);
601                 BlockPositioning.updateBlockView(diagram, updatedBlock);
602
603                 diagram.getView().clear(updatedBlock, true, true);
604
605                 // validate display errors
606                 diagram.getAsComponent().clearCellOverlays();
607                 diagram.getAsComponent().validateGraph();
608
609                 diagram.getView().validate();
610             } finally {
611                 diagram.getModel().endUpdate();
612             }
613         }
614     }
615
616     /**
617      * Hook method that creates the new edge for insertEdge. This implementation
618      * does not set the source and target of the edge, these are set when the
619      * edge is added to the model.
620      *
621      * @param parent
622      *            Cell that specifies the parent of the new edge.
623      * @param id
624      *            Optional string that defines the Id of the new edge.
625      * @param value
626      *            Object to be used as the user object.
627      * @param source
628      *            Cell that defines the source of the edge.
629      * @param target
630      *            Cell that defines the target of the edge.
631      * @param style
632      *            Optional string that defines the cell style.
633      * @return Returns the new edge to be inserted.
634      * @see com.mxgraph.view.mxGraph#createEdge(java.lang.Object,
635      *      java.lang.String, java.lang.Object, java.lang.Object,
636      *      java.lang.Object, java.lang.String)
637      */
638     @Override
639     public Object createEdge(Object parent, String id, Object value, Object source, Object target, String style) {
640         Object ret = null;
641
642         if (source instanceof BasicPort) {
643             BasicPort src = (BasicPort) source;
644             BasicLink link = null;
645
646             JavaController controller = new JavaController();
647             long uid = controller.createObject(Kind.LINK);
648             if (src.getType() == Type.EXPLICIT) {
649                 link = new ExplicitLink(uid);
650             } else if (src.getType() == Type.IMPLICIT) {
651                 link = new ImplicitLink(uid);
652             } else {
653                 link = new CommandControlLink(uid);
654             }
655
656             // allocate the associated geometry
657             link.setGeometry(new mxGeometry());
658             ret = link;
659         } else if (source instanceof SplitBlock) {
660             SplitBlock src = (SplitBlock) source;
661             return createEdge(parent, id, value, src.getIn(), target, style);
662         } else if (source instanceof BasicLink) {
663             BasicLink src = (BasicLink) source;
664             BasicLink link = null;
665
666             try {
667                 link = src.getClass().newInstance();
668
669                 // allocate the associated geometry
670                 link.setGeometry(new mxGeometry());
671
672             } catch (InstantiationException e) {
673                 LOG.severe(e.toString());
674             } catch (IllegalAccessException e) {
675                 LOG.severe(e.toString());
676             }
677
678             ret = link;
679         }
680
681         if (ret == null) {
682             ret = super.createEdge(parent, id, value, source, target, style);
683             LOG.warning("Creating a non typed edge");
684         }
685
686         return ret;
687     }
688
689     /**
690      * Add an edge from a source to the target.
691      *
692      * @param cell
693      *            the edge to add (may be null)
694      * @param parent
695      *            the parent of the source and the target
696      * @param source
697      *            the source cell
698      * @param target
699      *            the target cell
700      * @param index
701      *            the index of the edge
702      * @return the added edge or null.
703      * @see com.mxgraph.view.mxGraph#addEdge(java.lang.Object, java.lang.Object,
704      *      java.lang.Object, java.lang.Object, java.lang.Integer)
705      */
706     @Override
707     public Object addCell(Object cell, Object parent, Integer index, Object source, Object target) {
708
709         // already connected edge or normal block
710         if (source == null && target == null) {
711             return super.addCell(cell, parent, index, source, target);
712         }
713
714         // Command -> Control
715         if (source instanceof CommandPort && target instanceof ControlPort && cell instanceof CommandControlLink) {
716             return super.addCell(cell, parent, index, source, target);
717         }
718
719         // Control -> Command
720         // Switch source and target !
721         if (target instanceof CommandPort && source instanceof ControlPort && cell instanceof CommandControlLink) {
722             BasicLink current = (BasicLink) cell;
723             current.invertDirection();
724
725             return super.addCell(cell, parent, index, target, source);
726         }
727
728         // ExplicitOutput -> ExplicitInput
729         if (source instanceof ExplicitOutputPort && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
730             return super.addCell(cell, parent, index, source, target);
731         }
732         // ExplicitInput -> ExplicitOutput
733         // Switch source and target !
734         if (target instanceof ExplicitOutputPort && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
735             BasicLink current = (BasicLink) cell;
736             current.invertDirection();
737
738             return super.addCell(cell, parent, index, target, source);
739         }
740
741         // ImplicitOutput -> ImplicitInput
742         if (source instanceof ImplicitOutputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
743             return super.addCell(cell, parent, index, source, target);
744         }
745         // ImplicitInput -> ImplicitOutput
746         // Switch source and target !
747         if (target instanceof ImplicitOutputPort && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
748             BasicLink current = (BasicLink) cell;
749             current.invertDirection();
750
751             return super.addCell(cell, parent, index, target, source);
752         }
753
754         // ImplicitInput -> ImplicitInput
755         if (source instanceof ImplicitInputPort && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
756             return super.addCell(cell, parent, index, source, target);
757         }
758         // ImplicitOutputPort -> ImplicitOutput
759         // Switch source and target !
760         if (target instanceof ImplicitOutputPort && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
761             BasicLink current = (BasicLink) cell;
762             current.invertDirection();
763
764             return super.addCell(cell, parent, index, target, source);
765         }
766
767         /*
768          * Split management
769          */
770
771         // ExplicitLink -> ExplicitInputPort
772         if (source instanceof ExplicitLink && target instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
773             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
774             return addCell(cell, parent, index, split.getOut2(), target);
775         }
776         // ExplicitOutput -> ExpliciLink
777         // Switch source and target !
778         if (target instanceof ExplicitLink && source instanceof ExplicitInputPort && cell instanceof ExplicitLink) {
779             final BasicLink current = (BasicLink) cell;
780             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
781
782             current.invertDirection();
783
784             return addCell(cell, parent, index, split.getOut2(), source);
785         }
786
787         // ImplicitLink -> ImplicitInputPort
788         if (source instanceof ImplicitLink && target instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
789             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
790             return addCell(cell, parent, index, split.getOut2(), target);
791         }
792         // ImplicitInputPort -> ImplicitLink
793         // Switch source and target !
794         if (target instanceof ImplicitLink && source instanceof ImplicitInputPort && cell instanceof ImplicitLink) {
795             final BasicLink current = (BasicLink) cell;
796             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
797
798             current.invertDirection();
799
800             return addCell(cell, parent, index, split.getOut2(), source);
801         }
802
803         // ImplicitLink -> ImplicitOutputPort
804         if (source instanceof ImplicitLink && target instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
805             final BasicLink current = (BasicLink) cell;
806             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) source);
807             return addCell(cell, parent, index, split.getOut2(), source);
808         }
809         // ImplicitOutputPort -> ImplicitLink
810         // Switch source and target !
811         if (target instanceof ImplicitLink && source instanceof ImplicitOutputPort && cell instanceof ImplicitLink) {
812             final BasicLink current = (BasicLink) cell;
813             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (ImplicitLink) target);
814             return addCell(cell, parent, index, split.getOut2(), source);
815         }
816
817         // CommandControlLink -> ControlPort
818         if (source instanceof CommandControlLink && target instanceof ControlPort && cell instanceof CommandControlLink) {
819             SplitBlock split = addSplitEdge(((BasicLink) cell).getGeometry().getSourcePoint(), (BasicLink) source);
820             return addCell(cell, parent, index, split.getOut2(), target);
821         }
822         // ControlPort -> CommandControlLink
823         // Switch source and target !
824         if (target instanceof CommandControlLink && source instanceof ControlPort && cell instanceof CommandControlLink) {
825             final BasicLink current = (BasicLink) cell;
826             final SplitBlock split = addSplitEdge(current.getGeometry().getTargetPoint(), (BasicLink) target);
827
828             current.invertDirection();
829
830             return addCell(cell, parent, index, split.getOut2(), source);
831         }
832
833         if (cell instanceof BasicLink && source != null && target != null) {
834             LOG.severe("Unable to add a typed link");
835             return null;
836         } else {
837             LOG.severe("Adding an untyped edge");
838             return super.addCell(cell, parent, index, source, target);
839         }
840     }
841
842     /**
843      * Add a split on a edge.
844      *
845      * @param splitPoint
846      *            the split point (center of the split block)
847      * @param link
848      *            source link
849      * @return split block
850      */
851     public SplitBlock addSplitEdge(final mxPoint splitPoint, final BasicLink link) {
852         final BasicPort linkSource = (BasicPort) link.getSource();
853         final BasicPort linkTarget = (BasicPort) link.getTarget();
854
855         /*
856          * Select the right split accordingly to the link klass
857          */
858         BlockInterFunction f;
859         if (link instanceof CommandControlLink) {
860             f = BlockInterFunction.CLKSPLIT_f;
861         } else if (link instanceof ImplicitLink) {
862             f = BlockInterFunction.IMPSPLIT_f;
863         } else {
864             f = BlockInterFunction.SPLIT_f;
865         }
866
867         final SplitBlock splitBlock = (SplitBlock) XcosCellFactory.createBlock(f);
868
869         getModel().beginUpdate();
870         try {
871             // Origin of the parent, (0,0) as default may be different in case
872             mxPoint orig = link.getParent().getGeometry();
873             if (orig == null) {
874                 orig = new mxPoint();
875             }
876
877
878             addCell(splitBlock);
879             // force resize and align on the grid
880             resizeCell(splitBlock, new mxRectangle(splitPoint.getX(), splitPoint.getY(), 0, 0));
881
882             // Update old link
883
884             // get breaking segment and related point
885             mxPoint splitTr = new mxPoint(splitPoint.getX() - orig.getX(), splitPoint.getY() - orig.getY());
886             final int pos = link.findNearestSegment(splitTr);
887
888             // save points after breaking point
889             final List<mxPoint> saveStartPoints = link.getPoints(pos, true);
890             final List<mxPoint> saveEndPoints = link.getPoints(pos, false);
891
892             // remove the first end point if the position is near the split
893             // position
894             if (saveEndPoints.size() > 0) {
895                 final mxPoint p = saveEndPoints.get(0);
896                 final double dx = p.getX() - splitTr.getX();
897                 final double dy = p.getY() - splitTr.getY();
898
899                 if (!getAsComponent().isSignificant(dx, dy)) {
900                     saveEndPoints.remove(0);
901                 }
902             }
903
904             // disable events
905             getModel().beginUpdate();
906             getModel().remove(link);
907             getModel().endUpdate();
908
909             connect(linkSource, splitBlock.getIn(), saveStartPoints, orig);
910             connect(splitBlock.getOut1(), linkTarget, saveEndPoints, orig);
911
912             refresh();
913         } finally {
914             getModel().endUpdate();
915         }
916
917         return splitBlock;
918     }
919
920     /**
921      * Connect two port together with the associated points.
922      *
923      * This method perform the connection in two step in order to generate the
924      * right UndoableChangeEdits.
925      *
926      * @param src
927      *            the source port
928      * @param trg
929      *            the target port
930      * @param points
931      *            the points
932      * @param orig
933      *            the origin point (may be (0,0))
934      */
935     public void connect(BasicPort src, BasicPort trg, List<mxPoint> points, mxPoint orig) {
936         mxGeometry geometry;
937
938         /*
939          * Add the link with a default geometry
940          */
941         final Object newLink1 = createEdge(null, null, null, src, trg, null);
942         addCell(newLink1, null, null, src, trg);
943         geometry = getModel().getGeometry(newLink1);
944         if (getModel().getParent(newLink1) instanceof BasicBlock) {
945             // on a loop link, translate the points as the cell has been moved to the parent
946             orig.setX(orig.getX() + geometry.getX());
947             orig.setY(orig.getY() + geometry.getY());
948         }
949         geometry.setPoints(points);
950         getModel().setGeometry(newLink1, geometry);
951
952         /*
953          * Update the geometry
954          */
955         // should be cloned to generate an event
956         geometry = (mxGeometry) getModel().getGeometry(newLink1).clone();
957         final double dx = orig.getX();
958         final double dy = orig.getY();
959
960         geometry.translate(dx, dy);
961         getModel().setGeometry(newLink1, geometry);
962     }
963
964     /**
965      * Initialize component settings for a graph.
966      *
967      * This method *must* be used to setup the component after any
968      * reassociation.
969      */
970     public void initComponent() {
971         getAsComponent().setToolTips(true);
972
973         // This enable stop editing cells when pressing Enter.
974         getAsComponent().setEnterStopsCellEditing(false);
975
976         getAsComponent().setTolerance(1);
977
978         getAsComponent().getViewport().setOpaque(false);
979
980         getAsComponent().setBackground(XcosOptions.getEdition().getGraphBackground());
981
982         final boolean gridEnable = XcosOptions.getEdition().isGraphGridEnable();
983         setGridVisible(gridEnable);
984         if (gridEnable) {
985             setGridSize(XcosOptions.getEdition().getGraphGrid());
986         }
987
988         /*
989          * Reinstall related listeners
990          */
991
992         // Property change Listener
993         // Will say if a diagram has been modified or not.
994         final PropertyChangeListener p = new PropertyChangeListener() {
995             @Override
996             public void propertyChange(final PropertyChangeEvent e) {
997                 if (e.getPropertyName().compareTo(MODIFIED) == 0) {
998                     if (!e.getOldValue().equals(e.getNewValue())) {
999                         updateTabTitle();
1000                     }
1001                 }
1002             }
1003         };
1004         getAsComponent().removePropertyChangeListener(MODIFIED, p);
1005         getAsComponent().addPropertyChangeListener(MODIFIED, p);
1006     }
1007
1008     /**
1009      * Install the multiplicities (use for link checking)
1010      */
1011     private void setMultiplicities() {
1012         final List<mxMultiplicity> multiplicities = new ArrayList<mxMultiplicity>();
1013
1014         // Input data port
1015         multiplicities.add(new PortCheck(ExplicitInputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1016             private static final long serialVersionUID = -4987163442006736665L;
1017             {
1018                 add(ExplicitOutputPort.class);
1019                 add(ExplicitLink.class);
1020             }
1021         }), XcosMessages.LINK_ERROR_EXPLICIT_IN));
1022         multiplicities.add(new PortCheck(ImplicitInputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1023             private static final long serialVersionUID = 886376532181210926L;
1024             {
1025                 add(ImplicitOutputPort.class);
1026                 add(ImplicitInputPort.class);
1027                 add(ImplicitLink.class);
1028             }
1029         }), XcosMessages.LINK_ERROR_IMPLICIT_IN));
1030
1031         // Output data port
1032         multiplicities.add(new PortCheck(ExplicitOutputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1033             private static final long serialVersionUID = 4594127972486054821L;
1034             {
1035                 add(ExplicitInputPort.class);
1036             }
1037         }), XcosMessages.LINK_ERROR_EXPLICIT_OUT));
1038         multiplicities.add(new PortCheck(ImplicitOutputPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1039             private static final long serialVersionUID = -3719677806532507973L;
1040             {
1041                 add(ImplicitInputPort.class);
1042                 add(ImplicitOutputPort.class);
1043                 add(ImplicitLink.class);
1044             }
1045         }), XcosMessages.LINK_ERROR_IMPLICIT_OUT));
1046
1047         // Control port
1048         multiplicities.add(new PortCheck(ControlPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1049             private static final long serialVersionUID = 2941077191386058497L;
1050             {
1051                 add(CommandPort.class);
1052                 add(CommandControlLink.class);
1053             }
1054         }), XcosMessages.LINK_ERROR_EVENT_IN));
1055
1056         // Command port
1057         multiplicities.add(new PortCheck(CommandPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1058             private static final long serialVersionUID = -3470370027962480362L;
1059             {
1060                 add(ControlPort.class);
1061             }
1062         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1063
1064         // ExplicitLink connections
1065         multiplicities.add(new PortCheck(ExplicitLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1066             private static final long serialVersionUID = 7423543162930147373L;
1067
1068             {
1069                 add(ExplicitInputPort.class);
1070             }
1071         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1072
1073         // ImplicitLink connections
1074         multiplicities.add(new PortCheck(ImplicitLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1075             private static final long serialVersionUID = 7775100011122283282L;
1076
1077             {
1078                 add(ImplicitInputPort.class);
1079                 add(ImplicitOutputPort.class);
1080             }
1081         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1082
1083         // CommandControlLink connections
1084         multiplicities.add(new PortCheck(CommandControlLink.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1085             private static final long serialVersionUID = 3260421433507192386L;
1086
1087             {
1088                 add(ControlPort.class);
1089             }
1090         }), XcosMessages.LINK_ERROR_EVENT_OUT));
1091
1092         // Already connected port
1093         multiplicities.add(new PortCheck(BasicPort.class, Collections.unmodifiableList(new ArrayList < Class <? extends mxCell >> () {
1094             private static final long serialVersionUID = 6376349598052836660L;
1095
1096             {
1097                 add(BasicPort.class);
1098             }
1099         }), XcosMessages.LINK_ERROR_ALREADY_CONNECTED));
1100
1101         setMultiplicities(multiplicities.toArray(new mxMultiplicity[multiplicities.size()]));
1102     }
1103
1104     /**
1105      * Install all needed Listeners.
1106      */
1107     public void installListeners() {
1108         /*
1109          * First remove all listeners if present
1110          */
1111         removeListener(CellResizedTracker.getInstance());
1112         getUndoManager().removeListener(UndoUpdateTracker.getInstance());
1113         removeListener(RefreshBlockTracker.getInstance());
1114
1115         // Track when resizing a cell.
1116         addListener(mxEvent.CELLS_RESIZED, CellResizedTracker.getInstance());
1117
1118         // Update the blocks view on undo/redo
1119         getUndoManager().addListener(mxEvent.UNDO, UndoUpdateTracker.getInstance());
1120         getUndoManager().addListener(mxEvent.REDO, UndoUpdateTracker.getInstance());
1121
1122     }
1123
1124     /**
1125      * Removes the given cells from the graph including all connected edges if
1126      * includeEdges is true. The change is carried out using cellsRemoved.
1127      *
1128      * @param cells
1129      *            the cells to be removed
1130      * @param includeEdges
1131      *            true if the edges must be removed, false otherwise.
1132      * @return the deleted cells
1133      * @see com.mxgraph.view.mxGraph#removeCells(java.lang.Object[], boolean)
1134      */
1135     @Override
1136     public Object[] removeCells(final Object[] cells, final boolean includeEdges) {
1137         if (cells == null || cells.length == 0) {
1138             return super.removeCells(cells, includeEdges);
1139         }
1140
1141         /*
1142          * First remove all links connected to a removed Split if applicable
1143          */
1144         final Object[] initialCells;
1145         if (includeEdges) {
1146             initialCells = addAllEdges(cells);
1147         } else {
1148             initialCells = cells;
1149         }
1150
1151         // stash used on the loop
1152         final Queue<Object> loopCells = new LinkedList<Object>(Arrays.asList(initialCells));
1153         // the cells that need to be really
1154         final Set<Object> removedCells = new HashSet<Object>(loopCells);
1155         // couple of cells to reconnect
1156         final List<BasicPort[]> connectedCells = new ArrayList<BasicPort[]>();
1157         final List<List<mxPoint>> connectedPoints = new ArrayList<List<mxPoint>>();
1158
1159         /*
1160          * Then loop on the algorithm to select the right edges
1161          */
1162         // /!\ not bounded algorithm
1163         while (loopCells.size() > 0) {
1164             Object cell = loopCells.remove();
1165
1166             if (cell instanceof BasicLink) {
1167                 /*
1168                  * Continue on non fully connected links
1169                  */
1170                 if (((BasicLink) cell).getSource() == null) {
1171                     continue;
1172                 }
1173                 if (((BasicLink) cell).getTarget() == null) {
1174                     continue;
1175                 }
1176
1177                 /*
1178                  * Add any split to a link
1179                  */
1180                 addTerminalParent(((BasicLink) cell).getSource(), removedCells, loopCells);
1181                 addTerminalParent(((BasicLink) cell).getTarget(), removedCells, loopCells);
1182
1183             } else if (cell instanceof SplitBlock) {
1184                 final SplitBlock splitBlock = (SplitBlock) cell;
1185
1186                 /*
1187                  * Remove related connection or not and reconnect.
1188                  */
1189
1190                 if (splitBlock.getIn().getEdgeCount() == 0 || splitBlock.getOut1().getEdgeCount() == 0 || splitBlock.getOut2().getEdgeCount() == 0) {
1191                     // corner case, all links will be removed
1192                     continue;
1193                 }
1194
1195                 final mxICell inLink = splitBlock.getIn().getEdgeAt(0);
1196                 final mxICell out1Link = splitBlock.getOut1().getEdgeAt(0);
1197                 final mxICell out2Link = splitBlock.getOut2().getEdgeAt(0);
1198
1199                 final boolean inRemoved = removedCells.contains(inLink);
1200                 final boolean out1Removed = removedCells.contains(out1Link);
1201                 final boolean out2Removed = removedCells.contains(out2Link);
1202
1203                 /*
1204                  * Explicit case, if the in link is deleted; all the out links
1205                  * also should.
1206                  */
1207                 if (inLink instanceof ExplicitLink && inRemoved) {
1208                     if (removedCells.add(out1Link)) {
1209                         loopCells.add(out1Link);
1210                     }
1211
1212                     if (removedCells.add(out2Link)) {
1213                         loopCells.add(out2Link);
1214                     }
1215                 }
1216
1217                 /*
1218                  * Global case reconnect if not removed
1219                  */
1220                 final BasicPort[] connection;
1221                 List<mxPoint> points = null;
1222                 if (!inRemoved && !out1Removed && out2Removed) {
1223                     connection = findTerminals(inLink, out1Link, removedCells);
1224                     points = getDirectPoints(splitBlock, inLink, out1Link);
1225                 } else if (!inRemoved && out1Removed && !out2Removed) {
1226                     connection = findTerminals(inLink, out2Link, removedCells);
1227                     points = getDirectPoints(splitBlock, inLink, out2Link);
1228                 } else if (inRemoved && !out1Removed && !out2Removed) {
1229                     // only implicit or event case, log otherwise
1230                     if (out1Link instanceof ExplicitLink || out2Link instanceof ExplicitLink) {
1231                         LOG.severe("Reconnection failed for explicit links");
1232                         connection = null;
1233                     } else {
1234                         connection = findTerminals(out1Link, out2Link, removedCells);
1235                         points = getDirectPoints(splitBlock, out1Link, out2Link);
1236                     }
1237                 } else {
1238                     connection = null;
1239                 }
1240
1241                 if (connection != null) {
1242                     connectedCells.add(connection);
1243                     connectedPoints.add(points);
1244                 }
1245             }
1246         }
1247
1248         final Object[] ret;
1249         getModel().beginUpdate();
1250         try {
1251             ret = super.removeCells(removedCells.toArray(), includeEdges);
1252             for (int i = 0; i < connectedCells.size(); i++) {
1253                 final BasicPort[] connection = connectedCells.get(i);
1254                 final List<mxPoint> points = connectedPoints.get(i);
1255                 if (!removedCells.contains(connection[0].getParent()) && !removedCells.contains(connection[1].getParent())) {
1256                     connect(connection[0], connection[1], points, new mxPoint());
1257                 }
1258             }
1259         } finally {
1260             getModel().endUpdate();
1261         }
1262         return ret;
1263     }
1264
1265     /**
1266      * Add any terminal parent to the removed cells
1267      *
1268      * @param terminal
1269      *            the current terminal (instance of BasicPort)
1270      * @param removedCells
1271      *            the "to be removed" set
1272      * @param loopCells
1273      *            the "while loop" set
1274      */
1275     private void addTerminalParent(mxICell terminal, Collection<Object> removedCells, Collection<Object> loopCells) {
1276         assert (terminal == null || terminal instanceof BasicPort);
1277         assert (removedCells != null);
1278         assert (loopCells != null);
1279
1280         // getting terminal parent
1281         mxICell target = null;
1282         if (terminal != null) {
1283             target = terminal.getParent();
1284         } else {
1285             target = null;
1286         }
1287
1288         // add target if applicable
1289         if (target instanceof SplitBlock) {
1290             if (removedCells.add(target)) {
1291                 loopCells.add(target);
1292             }
1293         }
1294     }
1295
1296     /**
1297      * Find the terminals when relinking the 2 links
1298      *
1299      * This method ensure that {source, target} are not child of removed blocks.
1300      *
1301      * @param linkSource
1302      *            the normal source link
1303      * @param linkTerminal
1304      *            the normal target link
1305      * @param removedCells
1306      *            the set of removed objects
1307      * @return the {source, target} connection
1308      */
1309     private BasicPort[] findTerminals(final mxICell linkSource, final mxICell linkTerminal, final Set<Object> removedCells) {
1310         BasicPort src = (BasicPort) linkSource.getTerminal(true);
1311         BasicPort tgt = (BasicPort) linkTerminal.getTerminal(false);
1312         if (linkSource instanceof ImplicitLink) {
1313             if (removedCells.contains(src.getParent())) {
1314                 src = (BasicPort) linkSource.getTerminal(false);
1315             }
1316             if (removedCells.contains(tgt.getParent())) {
1317                 tgt = (BasicPort) linkTerminal.getTerminal(true);
1318             }
1319         }
1320
1321         return new BasicPort[] { src, tgt };
1322     }
1323
1324     /**
1325      * Get the direct points from inLink.getSource() to outLink.getTarget().
1326      *
1327      * @param splitBlock
1328      *            the current splitblock (added as a mid-point)
1329      * @param inLink
1330      *            the link before the split
1331      * @param outLink
1332      *            the link after the split
1333      * @return the points
1334      */
1335     private List<mxPoint> getDirectPoints(final SplitBlock splitBlock, final mxICell inLink, final mxICell outLink) {
1336         List<mxPoint> points;
1337         // add the points before the split
1338         points = new ArrayList<mxPoint>();
1339         if (inLink.getGeometry().getPoints() != null) {
1340             points.addAll(inLink.getGeometry().getPoints());
1341         }
1342
1343         // add a new point at the split location
1344         points.add(new mxPoint(snap(splitBlock.getGeometry().getCenterX()), snap(splitBlock.getGeometry().getCenterY())));
1345
1346         // add the points after the split
1347         if (outLink.getGeometry().getPoints() != null) {
1348             points.addAll(outLink.getGeometry().getPoints());
1349         }
1350
1351         return points;
1352     }
1353
1354     /**
1355      * Manage Group to be CellFoldable i.e with a (-) to reduce and a (+) to
1356      * expand them. Labels (mxCell instance with value) should not have a
1357      * visible foldable sign.
1358      *
1359      * @param cell
1360      *            the selected cell
1361      * @param collapse
1362      *            the collapse settings
1363      * @return always <code>false</code>
1364      * @see com.mxgraph.view.mxGraph#isCellFoldable(java.lang.Object, boolean)
1365      */
1366     @Override
1367     public boolean isCellFoldable(final Object cell, final boolean collapse) {
1368         return false;
1369     }
1370
1371     /**
1372      * Not BasicBLock cell have a moveable label.
1373      *
1374      * @param cell
1375      *            the cell
1376      * @return true if the corresponding label is moveable
1377      * @see com.mxgraph.view.mxGraph#isLabelMovable(java.lang.Object)
1378      */
1379     @Override
1380     public boolean isLabelMovable(Object cell) {
1381         return !(cell instanceof BasicBlock);
1382     }
1383
1384     /**
1385      * Return true if selectable
1386      *
1387      * @param cell
1388      *            the cell
1389      * @return status
1390      * @see com.mxgraph.view.mxGraph#isCellSelectable(java.lang.Object)
1391      */
1392     @Override
1393     public boolean isCellSelectable(final Object cell) {
1394         if (cell instanceof BasicPort) {
1395             return false;
1396         }
1397         return super.isCellSelectable(cell);
1398     }
1399
1400     /**
1401      * Return true if movable
1402      *
1403      * @param cell
1404      *            the cell
1405      * @return status
1406      * @see com.mxgraph.view.mxGraph#isCellMovable(java.lang.Object)
1407      */
1408     @Override
1409     public boolean isCellMovable(final Object cell) {
1410         if (cell instanceof BasicPort) {
1411             return false;
1412         }
1413
1414         boolean movable = false;
1415         final Object[] cells = getSelectionCells();
1416
1417         // don't move if selection is only links
1418         for (Object c : cells) {
1419             if (!(c instanceof BasicLink)) {
1420                 movable = true;
1421                 break;
1422             }
1423         }
1424
1425         return movable && super.isCellMovable(cell);
1426     }
1427
1428     /**
1429      * Return true if resizable
1430      *
1431      * @param cell
1432      *            the cell
1433      * @return status
1434      * @see com.mxgraph.view.mxGraph#isCellResizable(java.lang.Object)
1435      */
1436     @Override
1437     public boolean isCellResizable(final Object cell) {
1438         if (cell instanceof SplitBlock) {
1439             return false;
1440         }
1441         return (cell instanceof BasicBlock) && super.isCellResizable(cell);
1442     }
1443
1444     /**
1445      * A cell is deletable if it is not a locked block or an identifier cell
1446      *
1447      * @param cell
1448      *            the cell
1449      * @return status
1450      * @see com.mxgraph.view.mxGraph#isCellDeletable(java.lang.Object)
1451      */
1452     @Override
1453     public boolean isCellDeletable(final Object cell) {
1454         final boolean isALockedBLock = cell instanceof BasicBlock && ((BasicBlock) cell).isLocked();
1455         final boolean isAnIdentifier = cell.getClass().equals(mxCell.class);
1456
1457         if (isALockedBLock) {
1458             return false;
1459         }
1460         if (isAnIdentifier) {
1461             return true;
1462         }
1463
1464         return super.isCellDeletable(cell);
1465     }
1466
1467     /**
1468      * Return true if editable
1469      *
1470      * @param cell
1471      *            the cell
1472      * @return status
1473      * @see com.mxgraph.view.mxGraph#isCellEditable(java.lang.Object)
1474      */
1475     @Override
1476     public boolean isCellEditable(final Object cell) {
1477         return (cell instanceof TextBlock) && super.isCellDeletable(cell);
1478     }
1479
1480     /**
1481      * Return or create the identifier for the cell
1482      *
1483      * @param cell
1484      *            the cell to check
1485      * @return the identifier cell
1486      */
1487     public mxCell getOrCreateCellIdentifier(final mxCell cell) {
1488         if (cell.getId().endsWith(HASH_IDENTIFIER)) {
1489             return cell;
1490         }
1491
1492         final mxGraphModel graphModel = (mxGraphModel) getModel();
1493
1494         final mxCell identifier;
1495         final String cellId = cell.getId() + HASH_IDENTIFIER;
1496         if (graphModel.getCell(cellId) == null) {
1497             // create the identifier
1498             identifier = createCellIdentifier(cell);
1499
1500             // add the identifier
1501             graphModel.add(cell, identifier, cell.getChildCount());
1502         } else {
1503             identifier = (mxCell) graphModel.getCell(cellId);
1504         }
1505         return identifier;
1506     }
1507
1508     /**
1509      * Return the identifier for the cell
1510      *
1511      * @param cell
1512      *            the cell to check
1513      * @return the identifier cell
1514      */
1515     public mxCell getCellIdentifier(final mxCell cell) {
1516         final mxGraphModel graphModel = (mxGraphModel) getModel();
1517         final String cellId = cell.getId() + HASH_IDENTIFIER;
1518
1519         return (mxCell) graphModel.getCell(cellId);
1520     }
1521
1522     /**
1523      * Create a cell identifier for a specific cell
1524      *
1525      * @param cell
1526      *            the cell
1527      * @return the cell identifier.
1528      */
1529     public mxCell createCellIdentifier(final mxCell cell) {
1530         final mxCell identifier;
1531         final String cellId = cell.getId() + HASH_IDENTIFIER;
1532
1533         identifier = new mxCell(null, (mxGeometry) DEFAULT_LABEL_GEOMETRY.clone(), "noLabel=0;opacity=0;");
1534         identifier.getGeometry().setRelative(true);
1535         identifier.setVertex(true);
1536         identifier.setConnectable(false);
1537         identifier.setId(cellId);
1538
1539         return identifier;
1540     }
1541
1542     /**
1543      * Get the label for the cell according to its style.
1544      *
1545      * @param cell
1546      *            the cell object
1547      * @return a representative the string (block name) or a style specific
1548      *         style.
1549      * @see com.mxgraph.view.mxGraph#convertValueToString(java.lang.Object)
1550      */
1551     @Override
1552     public String convertValueToString(Object cell) {
1553         String ret = null;
1554
1555         if (cell != null) {
1556             JavaController controller = new JavaController();
1557             final Map<String, Object> style = getCellStyle(cell);
1558
1559             final String displayedLabel = (String) style.get("displayedLabel");
1560             if (displayedLabel != null) {
1561                 if (cell instanceof BasicBlock) {
1562                     try {
1563                         VectorOfDouble v = new VectorOfDouble();
1564                         controller.getObjectProperty(((BasicBlock) cell).getUID(), Kind.BLOCK, ObjectProperties.EXPRS, v);
1565
1566                         // FIXME : decode these exprs
1567                         //                        ret = String.format(displayedLabel, ((BasicBlock) cell).getExprsFormat());
1568                         ret = String.format(displayedLabel, "Not handled exprs ; please report a bug");
1569                     } catch (IllegalFormatException e) {
1570                         LOG.severe(e.toString());
1571                         ret = displayedLabel;
1572                     }
1573                 } else {
1574                     ret = displayedLabel;
1575                 }
1576             } else {
1577                 final String label = super.convertValueToString(cell);
1578                 if (label.isEmpty() && cell instanceof BasicBlock) {
1579                     String[] interfaceFunction = new String[1];
1580
1581                     controller.getObjectProperty(((BasicBlock) cell).getUID(), Kind.BLOCK, ObjectProperties.INTERFACE_FUNCTION, interfaceFunction);
1582                     ret = interfaceFunction[0];
1583                 } else {
1584                     ret = label;
1585                 }
1586             }
1587         }
1588
1589         return ret;
1590     }
1591
1592     /**
1593      * Return true if auto sized
1594      *
1595      * @param cell
1596      *            the cell
1597      * @return status
1598      * @see com.mxgraph.view.mxGraph#isAutoSizeCell(java.lang.Object)
1599      */
1600     @Override
1601     public boolean isAutoSizeCell(final Object cell) {
1602         boolean status = super.isAutoSizeCell(cell);
1603
1604         if (cell instanceof AfficheBlock) {
1605             status |= true;
1606         }
1607
1608         if (cell instanceof TextBlock) {
1609             status &= false;
1610         }
1611
1612         return status;
1613     }
1614
1615     /**
1616      * {@inheritDoc} Do not extends if the port position is north or south.
1617      */
1618     @Override
1619     public boolean isExtendParent(Object cell) {
1620         final boolean extendsParents;
1621
1622         if (cell instanceof BasicPort) {
1623             final BasicPort p = (BasicPort) cell;
1624             extendsParents = !(p.getOrientation() == Orientation.NORTH || p.getOrientation() == Orientation.SOUTH) && super.isExtendParent(p);
1625         } else {
1626             extendsParents = super.isExtendParent(cell);
1627         }
1628         return extendsParents;
1629     }
1630
1631     /**
1632      * @return the parameters
1633      */
1634     public ScicosParameters getScicosParameters() {
1635         return scicosParameters;
1636     }
1637
1638     /**
1639      * @return the model ID
1640      */
1641     public long getUID() {
1642         return uid;
1643     }
1644
1645     /**
1646      * @return Kind.DIAGRAM or Kind.BLOCK
1647      */
1648     public Kind getKind() {
1649         return kind;
1650     }
1651
1652     /**
1653      * Manage the visibility of the grid and the associated menu
1654      *
1655      * @param status
1656      *            new status
1657      */
1658     public void setGridVisible(final boolean status) {
1659         setGridEnabled(status);
1660         getAsComponent().setGridVisible(status);
1661         getAsComponent().repaint();
1662     }
1663
1664     /**
1665      * @return save status
1666      */
1667     public boolean saveDiagram() {
1668         final boolean isSuccess = saveDiagramAs(getSavedFile());
1669
1670         if (isSuccess) {
1671             setModified(false);
1672         }
1673
1674         return isSuccess;
1675     }
1676
1677     /**
1678      * @param fileName
1679      *            diagram filename
1680      * @return save status
1681      */
1682     public boolean saveDiagramAs(final File fileName) {
1683         boolean isSuccess = false;
1684         File writeFile = fileName;
1685         XcosFileType format = XcosOptions.getPreferences().getFileFormat();
1686
1687         info(XcosMessages.SAVING_DIAGRAM);
1688         if (fileName == null) {
1689             final SwingScilabFileChooser fc = SaveAsAction.createFileChooser();
1690             SaveAsAction.configureFileFilters(fc);
1691             ConfigurationManager.configureCurrentDirectory(fc);
1692
1693             if (getSavedFile() != null) {
1694                 // using save-as, the file chooser should have a filename
1695                 // without extension as default
1696                 String filename = getSavedFile().getName();
1697                 filename = filename.substring(0, filename.lastIndexOf('.'));
1698                 fc.setSelectedFile(new File(filename));
1699             } else {
1700                 final String title = getTitle();
1701                 if (title != null) {
1702                     /*
1703                      * Escape file to handle not supported character in file
1704                      * name (may be Windows only) see
1705                      * http://msdn.microsoft.com/en
1706                      * -us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
1707                      */
1708                     final char[] regex = "<>:\"/\\|?*".toCharArray();
1709                     String escaped = title;
1710                     for (char c : regex) {
1711                         escaped = escaped.replace(c, '-');
1712                     }
1713
1714                     fc.setSelectedFile(new File(escaped));
1715                 }
1716             }
1717
1718             int status = fc.showSaveDialog(this.getAsComponent());
1719             if (status != JFileChooser.APPROVE_OPTION) {
1720                 info(XcosMessages.EMPTY_INFO);
1721                 return isSuccess;
1722             }
1723
1724             // change the format if the user choose a save-able file format
1725             final XcosFileType selectedFilter = XcosFileType.findFileType(fc.getFileFilter());
1726             if (XcosFileType.getAvailableSaveFormats().contains(selectedFilter)) {
1727                 format = selectedFilter;
1728             }
1729             writeFile = fc.getSelectedFile();
1730         }
1731
1732         /* Extension/format update */
1733
1734         // using a String filename also works on a non-existing file
1735         final String filename = writeFile.getName();
1736
1737         /*
1738          * Look for the user extension if it does not exists, append a default
1739          * one
1740          *
1741          * if the specified extension is handled, update the save format ; else
1742          * append a default extension and use the default format
1743          */
1744         XcosFileType userExtension = XcosFileType.findFileType(filename);
1745         if (userExtension == null) {
1746             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1747             userExtension = format;
1748         }
1749         if (XcosFileType.getAvailableSaveFormats().contains(userExtension)) {
1750             format = userExtension;
1751         } else {
1752             writeFile = new File(writeFile.getParent(), filename + format.getDottedExtension());
1753         }
1754
1755         /*
1756          * If the file exists, ask for confirmation if this is not the
1757          * previously saved file
1758          */
1759         if (writeFile.exists() && !writeFile.equals(getSavedFile())) {
1760             final boolean overwrite = ScilabModalDialog.show(XcosTab.get(this), XcosMessages.OVERWRITE_EXISTING_FILE, XcosMessages.XCOS,
1761                                       IconType.QUESTION_ICON, ButtonType.YES_NO) == AnswerOption.YES_OPTION;
1762
1763             if (!overwrite) {
1764                 info(XcosMessages.EMPTY_INFO);
1765                 return false;
1766             }
1767         }
1768
1769         /*
1770          * Really save the data
1771          */
1772         try {
1773             format.save(writeFile.getCanonicalPath(), getRootDiagram());
1774             setSavedFile(writeFile);
1775
1776             setTitle(writeFile.getName().substring(0, writeFile.getName().lastIndexOf('.')));
1777             ConfigurationManager.getInstance().addToRecentFiles(writeFile);
1778             setModified(false);
1779             isSuccess = true;
1780         } catch (final Exception e) {
1781             LOG.severe(e.toString());
1782
1783             XcosDialogs.couldNotSaveFile(this);
1784         }
1785
1786         info(XcosMessages.EMPTY_INFO);
1787         return isSuccess;
1788     }
1789
1790     /**
1791      * Perform post loading initialization.
1792      *
1793      * @param file
1794      *            the loaded file
1795      */
1796     public void postLoad(final File file) {
1797         final String name = file.getName();
1798
1799         if (XcosFileType.getAvailableSaveFormats().contains(XcosFileType.findFileType(file))) {
1800             setSavedFile(file);
1801         }
1802         setTitle(name.substring(0, name.lastIndexOf('.')));
1803         setModified(false);
1804
1805         fireEvent(new mxEventObject(mxEvent.ROOT));
1806
1807         info(XcosMessages.EMPTY_INFO);
1808     }
1809
1810     /**
1811      * Set the title of the diagram
1812      *
1813      * @param title
1814      *            the title
1815      * @see org.scilab.modules.graph.ScilabGraph#setTitle(java.lang.String)
1816      */
1817     @Override
1818     public void setTitle(final String title) {
1819         super.setTitle(title);
1820         updateTabTitle();
1821
1822         JavaController controller = new JavaController();
1823         controller.setObjectProperty(getUID(), getKind(), ObjectProperties.TITLE, title);
1824     }
1825
1826     /**
1827      * Update the title
1828      */
1829     public void updateTabTitle() {
1830         // get the modifier string
1831         final String modified;
1832         if (isModified()) {
1833             modified = "*";
1834         } else {
1835             modified = "";
1836         }
1837
1838         // get the title string
1839         final String title = getTitle();
1840
1841         // get the path
1842         CharSequence formattedPath = "";
1843         final File savedFile = getSavedFile();
1844         if (savedFile != null) {
1845             try {
1846                 final String path = savedFile.getCanonicalPath();
1847                 formattedPath = new StringBuilder().append(" (").append(path).append(')');
1848             } catch (final IOException e) {
1849                 LOG.warning(e.toString());
1850             }
1851         }
1852
1853         // Product name
1854         final String product = Xcos.TRADENAME;
1855
1856         final String tabTitle = new StringBuilder().append(modified).append(title).append(formattedPath).append(" - ").append(product).toString();
1857
1858         final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(getGraphTab());
1859         if (tab != null) {
1860             tab.setName(tabTitle);
1861         }
1862     }
1863
1864     /**
1865      * Load a file with different method depending on it extension
1866      *
1867      * @param controller the used controller
1868      * @param file
1869      *            File to load (can be null)
1870      */
1871     public void transformAndLoadFile(final JavaController controller, final String file) {
1872         final File f;
1873         final XcosFileType filetype;
1874         if (file != null) {
1875             f = new File(file);
1876             filetype = XcosFileType.findFileType(f);
1877         } else {
1878             f = null;
1879             filetype = null;
1880         }
1881         new SwingWorker<XcosDiagram, ActionEvent>() {
1882             int counter = 0;
1883             final Timer t = new Timer(1000, new ActionListener() {
1884                 @Override
1885                 public void actionPerformed(ActionEvent e) {
1886                     counter = (counter + 1) % (XcosMessages.DOTS.length() + 1);
1887                     String str = XcosMessages.LOADING_DIAGRAM + XcosMessages.DOTS.substring(0, counter);
1888
1889                     XcosDiagram.this.info(str);
1890                 }
1891             });
1892
1893             @Override
1894             protected XcosDiagram doInBackground() {
1895                 t.start();
1896
1897                 final Xcos instance = Xcos.getInstance();
1898                 XcosDiagram diag = XcosDiagram.this;
1899
1900                 diag.setReadOnly(true);
1901
1902                 /*
1903                  * Load, log errors and notify
1904                  */
1905                 synchronized (instance) {
1906                     try {
1907
1908                         if (f != null && filetype != null) {
1909                             filetype.load(file, XcosDiagram.this);
1910                         } else {
1911                             //                          FIXME: implement the model decoding
1912                             //                          controller.getObjectProperty(uid, k, p, v)
1913                         }
1914
1915                         instance.setLastError("");
1916                     } catch (Exception e) {
1917                         Throwable ex = e;
1918                         while (ex instanceof RuntimeException) {
1919                             ex = ex.getCause();
1920                         }
1921                         instance.setLastError(ex.getMessage());
1922                     }
1923                     instance.notify();
1924                 }
1925
1926                 return diag;
1927             }
1928
1929             @Override
1930             protected void done() {
1931                 t.stop();
1932                 XcosDiagram.this.setReadOnly(false);
1933                 XcosDiagram.this.getUndoManager().clear();
1934                 XcosDiagram.this.refresh();
1935
1936                 /*
1937                  * Load has finished
1938                  */
1939                 if (f != null && filetype != null) {
1940                     postLoad(f);
1941                 }
1942                 XcosDiagram.this.info(XcosMessages.EMPTY_INFO);
1943             }
1944
1945         } .execute();
1946     }
1947
1948     /**
1949      * Getting the root diagram of a decomposed diagram
1950      *
1951      * @return Root parent of the whole parent
1952      */
1953     public XcosDiagram getRootDiagram() {
1954         JavaController controller = new JavaController();
1955         long[] parent = new long[1];
1956         controller.getObjectProperty(uid, kind, ObjectProperties.PARENT_DIAGRAM, parent);
1957
1958         Collection<XcosDiagram> diagrams = Xcos.getInstance().getDiagrams(parent[0]);
1959         return diagrams.stream().filter(d -> d.getUID() == parent[0])
1960                .findFirst().get();
1961     }
1962
1963     /**
1964      * Returns the tooltip to be used for the given cell.
1965      *
1966      * @param cell
1967      *            block
1968      * @return cell tooltip
1969      */
1970     @Override
1971     public String getToolTipForCell(final Object cell) {
1972         if (cell instanceof XcosCell) {
1973             return String.valueOf(((XcosCell) cell).getUID());
1974         }
1975         return "";
1976     }
1977
1978     /**
1979      * Display the message in info bar.
1980      *
1981      * @param message
1982      *            Information
1983      */
1984     public void info(final String message) {
1985         final XcosTab tab = XcosTab.get(this);
1986
1987         if (tab != null && tab.getInfoBar() != null) {
1988             tab.getInfoBar().setText(message);
1989         }
1990     }
1991
1992     /**
1993      * Display the message into an error popup
1994      *
1995      * @param message
1996      *            Error of the message
1997      */
1998     public void error(final String message) {
1999         JOptionPane.showMessageDialog(getAsComponent(), message, XcosMessages.XCOS, JOptionPane.ERROR_MESSAGE);
2000     }
2001
2002     /**
2003      * Find the block corresponding to the given uid and display a warning
2004      * message.
2005      *
2006      * @param uid
2007      *            - A String as UID.
2008      * @param message
2009      *            - The message to display.
2010      */
2011     public void warnCellByUID(final String uid, final String message) {
2012         final Object cell = ((mxGraphModel) getModel()).getCell(uid);
2013         if (cell == null) {
2014             return;
2015         }
2016
2017         if (GraphicsEnvironment.isHeadless()) {
2018             System.err.printf("%s at %s\n    %s: %s\n", "warnCell", getRootDiagram().getTitle(), uid, message);
2019             return;
2020         }
2021
2022         // open the tab
2023         if (XcosTab.get(this) == null) {
2024             XcosTab.restore(this);
2025         }
2026
2027         if (message.isEmpty()) {
2028             getAsComponent().clearCellOverlays(cell);
2029         } else {
2030             getAsComponent().setCellWarning(cell, message, null, true);
2031         }
2032     }
2033
2034     @Override
2035     public File getSavedFile() {
2036         if (kind == Kind.DIAGRAM) {
2037             return super.getSavedFile();
2038         } else {
2039             return getRootDiagram().getSavedFile();
2040         }
2041     }
2042
2043     /**
2044      * Set the current diagram in a modified state
2045      *
2046      * @param modified
2047      *            True or False whether the current diagram must be saved or
2048      *            not.
2049      */
2050     @Override
2051     public void setModified(final boolean modified) {
2052         super.setModified(modified);
2053         updateTabTitle();
2054     }
2055
2056     /**
2057      * Evaluate the current context
2058      *
2059      * @return The resulting data. Keys are variable names and Values are
2060      *         evaluated values.
2061      */
2062     public Map<String, String> evaluateContext() {
2063         Map<String, String> result = Collections.emptyMap();
2064         // FIXME: evaluate the context on scilab 6 ?
2065         //        final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
2066         //        if (handler == null) {
2067         //            return result;
2068         //        }
2069         //
2070         //        try {
2071         //            // first write the context strings
2072         //            handler.writeContext(getContext());
2073         //
2074         //            // evaluate using script2var
2075         //            ScilabInterpreterManagement.synchronousScilabExec(ScilabDirectHandler.CONTEXT + " = script2var(" + ScilabDirectHandler.CONTEXT + ", struct());");
2076         //
2077         //            // read the structure
2078         //            result = handler.readContext();
2079         //        } catch (final InterpreterException e) {
2080         //            info("Unable to evaluate the contexte");
2081         //            e.printStackTrace();
2082         //        } finally {
2083         //            handler.release();
2084         //        }
2085
2086         return result;
2087     }
2088
2089     /**
2090      * Returns true if the given cell is a not a block nor a port.
2091      *
2092      * @param cell
2093      *            the drop target
2094      * @param cells
2095      *            the cells to be dropped
2096      * @return the drop status
2097      * @see com.mxgraph.view.mxGraph#isValidDropTarget(java.lang.Object,
2098      *      java.lang.Object[])
2099      */
2100     @Override
2101     public boolean isValidDropTarget(final Object cell, final Object[] cells) {
2102         return !(cell instanceof BasicBlock) && !(cell instanceof BasicBlock) && !(cell instanceof BasicPort) && super.isValidDropTarget(cell, cells);
2103     }
2104
2105     /**
2106      * Construct a new selection model used on this graph.
2107      *
2108      * @return a new selection model instance.
2109      * @see com.mxgraph.view.mxGraph#createSelectionModel()
2110      */
2111     @Override
2112     protected mxGraphSelectionModel createSelectionModel() {
2113         return new mxGraphSelectionModel(this) {
2114             /**
2115              * When we only want to select a cell which is a port, select the
2116              * parent block.
2117              *
2118              * @param cell
2119              *            the cell
2120              * @see com.mxgraph.view.mxGraphSelectionModel#setCell(java.lang.Object)
2121              */
2122             @Override
2123             public void setCell(final Object cell) {
2124                 final Object current;
2125                 if (cell instanceof BasicPort) {
2126                     current = getModel().getParent(cell);
2127                 } else {
2128                     current = cell;
2129                 }
2130                 super.setCell(current);
2131             }
2132         };
2133     }
2134
2135     @Override
2136     protected void finalize() throws Throwable {
2137         JavaController controller = new JavaController();
2138         controller.deleteObject(uid);
2139     }
2140 }