Scicos: speedup model transformation
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / Xcos.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009 - DIGITEO - Bruno JOFRET
4  * Copyright (C) 2010 - DIGITEO - Clement DAVID
5  * Copyright (C) 2011-2017 - Scilab Enterprises - Clement DAVID
6  *
7  * Copyright (C) 2012 - 2016 - Scilab Enterprises
8  *
9  * This file is hereby licensed under the terms of the GNU GPL v2.0,
10  * pursuant to article 5.3.4 of the CeCILL v.2.1.
11  * This file was originally licensed under the terms of the CeCILL v2.1,
12  * and continues to be available under such terms.
13  * For more information, see the COPYING file which you should have received
14  * along with this program.
15  *
16  */
17
18 package org.scilab.modules.xcos;
19
20 import java.awt.Component;
21 import java.io.File;
22 import java.io.IOException;
23 import java.lang.reflect.InvocationTargetException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.logging.LogManager;
33 import java.util.logging.Logger;
34
35 import javax.swing.Action;
36 import javax.swing.ImageIcon;
37 import javax.swing.SwingUtilities;
38
39 import org.scilab.modules.action_binding.InterpreterManagement;
40 import org.scilab.modules.commons.gui.FindIconHelper;
41 import org.scilab.modules.commons.xml.XConfiguration;
42 import org.scilab.modules.core.Scilab;
43 import org.scilab.modules.graph.actions.base.GraphActionManager;
44 import org.scilab.modules.graph.utils.ScilabExported;
45 import org.scilab.modules.gui.bridge.menu.SwingScilabMenu;
46 import org.scilab.modules.gui.bridge.menubar.SwingScilabMenuBar;
47 import org.scilab.modules.gui.bridge.tab.SwingScilabDockablePanel;
48 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
49 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
50 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
51 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
52 import org.scilab.modules.gui.tabfactory.AbstractScilabTabFactory;
53 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
54 import org.scilab.modules.gui.utils.BarUpdater;
55 import org.scilab.modules.gui.utils.ClosingOperationsManager;
56 import org.scilab.modules.gui.utils.WindowsConfigurationManager;
57 import org.scilab.modules.localization.Messages;
58 import org.scilab.modules.xcos.actions.ExternalAction;
59 import org.scilab.modules.xcos.actions.StopAction;
60 import org.scilab.modules.xcos.configuration.ConfigurationManager;
61 import org.scilab.modules.xcos.configuration.model.DocumentType;
62 import org.scilab.modules.xcos.graph.DiagramComparator;
63 import org.scilab.modules.xcos.graph.XcosDiagram;
64 import org.scilab.modules.xcos.io.XcosFileType;
65 import org.scilab.modules.xcos.palette.PaletteManager;
66 import org.scilab.modules.xcos.palette.view.PaletteManagerView;
67 import org.scilab.modules.xcos.preferences.XcosConfiguration;
68 import org.scilab.modules.xcos.utils.FileUtils;
69 import org.scilab.modules.xcos.utils.XcosMessages;
70
71 import com.mxgraph.model.mxCell;
72 import com.mxgraph.model.mxGraphModel;
73 import com.mxgraph.model.mxICell;
74 import com.mxgraph.util.mxEvent;
75 import com.mxgraph.util.mxEventObject;
76 import com.mxgraph.view.mxStylesheet;
77 import javax.swing.Timer;
78 import org.scilab.modules.commons.ScilabCommons;
79 import org.scilab.modules.xcos.graph.model.ScicosObjectOwner;
80 import org.scilab.modules.xcos.graph.model.XcosGraphModel;
81
82 /**
83  * Xcos entry point class
84  */
85 // CSOFF: ClassFanOutComplexity
86 // CSOFF: ClassDataAbstractionCoupling
87 public final class Xcos {
88     /**
89      * The current Xcos version
90      */
91     public static final String VERSION = "2.0";
92     /**
93      * The current Xcos tradename
94      */
95     public static final String TRADENAME = Xcos.class.getSimpleName();
96     public static final ImageIcon ICON = new ImageIcon(FindIconHelper.findIcon("utilities-system-monitor", "256x256"));
97
98     private static final String LOAD_XCOS_LIBS_LOAD_SCICOS = "prot=funcprot(); funcprot(0); loadXcosLibs(); loadScicos(); funcprot(prot); clear prot";
99
100     /*
101      * Dependencies version
102      */
103     private static final List<String> MXGRAPH_VERSIONS = null;
104     private static final List<String> BATIK_VERSIONS = Arrays.asList("1.7", "1.8pre", "1.8");
105
106     private static final String UNABLE_TO_LOAD_JGRAPHX = Messages.gettext("Unable to load the jgraphx library.\nExpecting version %s ; Getting version %s .");
107     private static final String UNABLE_TO_LOAD_BATIK = Messages.gettext("Unable to load the Batik library. \nExpecting version %s ; Getting version %s .");
108
109     private static final String CALLED_OUTSIDE_THE_EDT_THREAD = "Called outside the EDT thread.";
110     private static final Logger LOG = Logger.getLogger(Xcos.class.getName());
111
112     /** common shared instance */
113     private static volatile Xcos sharedInstance;
114
115     static {
116         Scilab.registerInitialHook(() -> {
117             // wait the Scilab startup termination
118             final Timer t = new Timer(500, null);
119             t.addActionListener((e) -> {
120                 if (ScilabCommons.getStartProcessing() == 1)
121                     return;
122                 t.stop();
123
124                 /* load scicos libraries (macros) */
125                 InterpreterManagement.requestScilabExec(LOAD_XCOS_LIBS_LOAD_SCICOS);
126             });
127             t.start();
128         });
129
130         XConfiguration.addXConfigurationListener(new XcosConfiguration());
131     }
132
133     /*
134      * Instance data
135      */
136     private final Map<ScicosObjectOwner, List<XcosDiagram>> diagrams;
137     private XcosView view;
138     private BrowserView browser;
139     private boolean onDiagramIteration = false;
140     private String lastError = null;
141
142     /*
143      * Instance handlers
144      */
145     private final PaletteManager palette;
146     private final ConfigurationManager configuration;
147     private final mxStylesheet styleSheet;
148     private final List<ExternalAction> externalActions;
149
150     private final XcosTabFactory factory;
151
152     /**
153      * Construct an Xcos instance.
154      *
155      * There must be only one Xcos instance per Scilab application
156      */
157     private Xcos(final XcosTabFactory factory) {
158         /*
159          * Read the configuration to support dynamic (before Xcos launch) settings.
160          */
161         try {
162             LogManager.getLogManager().readConfiguration();
163         } catch (final SecurityException | IOException e) {
164             LOG.severe(e.toString());
165         }
166
167         /* Check the dependencies at startup time */
168         checkDependencies();
169
170         /*
171          * Allocate data
172          */
173         diagrams = new HashMap<>();
174
175         view = new XcosView();
176         // allocate and install the browser view on demand to avoid any cost
177         browser = null;
178
179         /*
180          * get the handlers instance
181          */
182         palette = PaletteManager.getInstance();
183         configuration = ConfigurationManager.getInstance();
184         styleSheet = new mxStylesheet();
185         externalActions = new ArrayList<>();
186
187         try {
188             FileUtils.decodeStyle(styleSheet);
189         } catch (final IOException e) {
190             LOG.severe(e.toString());
191         }
192
193         /*
194          * Register as an AbstractScilabTabFactory
195          */
196         if (factory == null) {
197             this.factory = new XcosTabFactory(false);
198         } else {
199             this.factory = factory;
200         }
201         ScilabTabFactory.getInstance().addTabFactory(this.factory);
202
203     }
204
205     @Override
206     protected void finalize() throws Throwable {
207         if (browser != null) {
208             JavaController.unregister_view(browser);
209         }
210
211         JavaController.unregister_view(view);
212
213         super.finalize();
214     }
215
216     /**
217      * Check the dependencies and the version dependencies.
218      *
219      * This method use runtime class loading to handle ClassNotFoundException.
220      *
221      * This method catch any exception and rethrow it with a well defined message. Thus it doesn't pass the IllegalCatch metrics.
222      */
223     // CSOFF: IllegalCatch
224     // CSOFF: MagicNumber
225     private void checkDependencies() {
226         final ClassLoader loader = ClassLoader.getSystemClassLoader();
227
228         /* JGraphx */
229         String mxGraphVersion = "";
230         try {
231             final Class<?> klass = loader.loadClass("com.mxgraph.view.mxGraph");
232             mxGraphVersion = (String) klass.getDeclaredField("VERSION").get(null);
233
234             if (MXGRAPH_VERSIONS != null && !MXGRAPH_VERSIONS.contains(mxGraphVersion)) {
235                 throw new Exception();
236             }
237         } catch (final Throwable e) {
238             throw new RuntimeException(String.format(UNABLE_TO_LOAD_JGRAPHX, MXGRAPH_VERSIONS.get(0), mxGraphVersion), e);
239         }
240
241         /* Batik */
242         String batikVersion = null;
243         try {
244             final Class<?> klass = loader.loadClass("org.apache.batik.Version");
245             batikVersion = klass.getPackage().getImplementationVersion().split("\\+")[0];
246
247             if (!BATIK_VERSIONS.contains(batikVersion)) {
248                 throw new Exception();
249             }
250
251         } catch (final Throwable e) {
252             throw new RuntimeException(String.format(UNABLE_TO_LOAD_BATIK, BATIK_VERSIONS.get(0), batikVersion), e);
253         }
254     }
255
256     // CSON: MagicNumber
257     // CSON: IllegalCatch
258
259     /**
260      * @return the per Scilab application, Xcos instance
261      */
262     public static synchronized Xcos getInstance() {
263         return getInstance(null);
264     }
265
266     /**
267      * @param factory
268      *            the tab factory instance or null on creation
269      * @return the per Scilab application, Xcos instance
270      */
271     private static synchronized Xcos getInstance(final XcosTabFactory factory) {
272         if (sharedInstance == null) {
273             try {
274                 if (!SwingUtilities.isEventDispatchThread()) {
275                     SwingUtilities.invokeAndWait(() -> {
276                         sharedInstance = new Xcos(factory);
277                     });
278                 } else {
279                     sharedInstance = new Xcos(factory);
280                 }
281             } catch (InvocationTargetException e) {
282                 e.printStackTrace();
283             } catch (InterruptedException e) {
284                 e.printStackTrace();
285             }
286
287             LOG.finest("Session started");
288         }
289
290         return sharedInstance;
291     }
292
293     /**
294      * Try to quit xcos
295      */
296     public void quit(boolean force) {
297         if (sharedInstance == null) {
298             return;
299         }
300
301         // TODO : perform something ?
302     }
303
304     /**
305      * Clear the shared instance.
306      */
307     private static synchronized void clearInstance() {
308         sharedInstance = null;
309         LOG.finest("Session ended");
310     }
311
312     /**
313      * All Opened diagrams
314      *
315      * @return the opened diagrams list
316      */
317     public List<XcosDiagram> openedDiagrams() {
318         ArrayList<XcosDiagram> opened = new ArrayList<>();
319         diagrams.values().stream().forEach(l -> opened.addAll(l));
320         return opened;
321     }
322
323     /**
324      * Opened diagrams
325      *
326      * @param root
327      *            the root diagram
328      * @return the opened diagrams list
329      */
330     public List<XcosDiagram> openedDiagrams(ScicosObjectOwner root) {
331         return diagrams.getOrDefault(root, Collections.emptyList());
332     }
333
334     public ScicosObjectOwner openedDiagram(File f) {
335         ScicosObjectOwner opened = null;
336         if (f == null) {
337             return opened;
338         }
339
340         for (ScicosObjectOwner root : diagrams.keySet()) {
341             List<XcosDiagram> diags = diagrams.getOrDefault(root, Collections.emptyList());
342
343             if (!diags.isEmpty() && f.equals(diags.get(0).getSavedFile())) {
344                 opened = root;
345                 break;
346             }
347         }
348
349         return opened;
350     }
351
352     /**
353      * Check if the in memory file representation is modified
354      *
355      * @param root
356      *            the root diagram
357      * @return is modified
358      */
359     public boolean isModified(ScicosObjectOwner root) {
360         for (XcosDiagram d : diagrams.get(root)) {
361             if (d.isModified()) {
362                 return true;
363             }
364         }
365
366         return false;
367     }
368
369     /**
370      * Set the full diagram as modified
371      * @param root the root diagram
372      * @param modified the modified status
373      */
374     public void setModified(ScicosObjectOwner root, boolean modified) {
375         for (XcosDiagram d : diagrams.get(root)) {
376             d.setModified(modified);
377         }
378     }
379
380     /**
381      * Popup a dialog to ask for a file creation
382      *
383      * @param f
384      *            the file to create
385      * @return true if creation is has been performed
386      */
387     public boolean askForFileCreation(final XcosDiagram diag, final File f) {
388         AnswerOption answer;
389         try {
390             answer = ScilabModalDialog.show(diag.getAsComponent(), new String[] { String.format(XcosMessages.FILE_DOESNT_EXIST, f.getCanonicalFile()) },
391                                             XcosMessages.XCOS, IconType.QUESTION_ICON, ButtonType.YES_NO);
392         } catch (final IOException e) {
393             LOG.severe(e.toString());
394             answer = AnswerOption.YES_OPTION;
395         }
396
397         if (answer == AnswerOption.YES_OPTION) {
398             return diag.saveDiagramAs(f);
399         }
400
401         return false;
402     }
403
404     /**
405      * @return the global shared styleSheet
406      */
407     public mxStylesheet getStyleSheet() {
408         return styleSheet;
409     }
410
411     /**
412      * Open a file from it's filename.
413      *
414      * This method must be called on the EDT thread. For other use, please use the {@link #xcos(String, String)} method.
415      *
416      * @param file
417      *            the file to open. If null an empty diagram is created.
418      * @param diagramId
419      *            the MVC ID to track. If 0 no association is performed.
420      */
421     public void open(final String file, final long diagramId) {
422         if (!SwingUtilities.isEventDispatchThread()) {
423             LOG.severe(CALLED_OUTSIDE_THE_EDT_THREAD);
424         }
425
426         /*
427          * If it is the first window opened, then open the palette first.
428          */
429         boolean openThePalette = openedDiagrams().isEmpty();
430
431         JavaController controller = new JavaController();
432         XcosDiagram diag = null;
433         final File f;
434         if (file != null) {
435             f = new File(file);
436         } else {
437             f = null;
438         }
439
440         if (f != null && f.exists()) {
441             configuration.addToRecentFiles(f);
442         }
443
444         /*
445          * looking for an already opened diagram
446          */
447         ScicosObjectOwner root = openedDiagram(f);
448         if (root != null) {
449             diag = diagrams.get(root).iterator().next();
450         }
451         // if unsaved and empty, reuse it. Allocate otherwise.
452         if (f == null && diag != null && diag.getModel().getChildCount(diag.getDefaultParent()) > 0) {
453             diag = null;
454         }
455
456         // looking for an empty, unsaved diagram to use if opening a new file
457         // if not found an already open instance of the file
458         if (diag == null) {
459             // traverse through the key set of all the opened diagrams
460             for (Map.Entry<ScicosObjectOwner, List<XcosDiagram>> entry : diagrams.entrySet()) {
461                 List<XcosDiagram> diagramsWithKey = entry.getValue();
462                 XcosDiagram diagramWithKey = diagramsWithKey.get(0); // get the diagram that maps to that key
463                 int childCount = diagramWithKey.countChildren(); //count the number of children in the diagram
464                 // if empty, unsaved and unused
465                 if (childCount == 0 && diagramWithKey.getSavedFile() == null && !diagramWithKey.isModified()) {
466                     // use that open diagram
467                     diag = diagramWithKey;
468                     diag.transformAndLoadFile(controller, file);
469                 }
470             }
471         }
472         // if reuse then request focus
473         if (diag != null) {
474             XcosTab tab = XcosTab.get(diag);
475             if (tab != null) {
476                 tab.setCurrent();
477                 tab.requestFocus();
478             }
479         }
480
481         final long currentId;
482         if (diagramId != 0) {
483             currentId = diagramId;
484         } else {
485             currentId = controller.createObject(Kind.DIAGRAM);
486         }
487
488         if (diag != null) {
489             // loading disabled, unlock
490             synchronized (this) {
491                 setLastError("");
492                 notify();
493             }
494         } else {
495             // loading enable, unlock will be performed later, on another thread
496
497             /*
498              * Allocate and setup a new diagram
499              */
500             diag = new XcosDiagram(controller, currentId, controller.getKind(currentId), "");
501             diag.installListeners();
502
503             root = findRoot(controller, diag);
504             addDiagram(root, diag);
505
506             /*
507              * Ask for file creation
508              */
509             if (f != null && !f.exists()) {
510                 if (!askForFileCreation(diag, f)) {
511                     // loading disabled, unlock
512                     synchronized (this) {
513                         setLastError("");
514                         notify();
515                     }
516
517                     // return now, to avoid tab creation
518                     diagrams.remove(root);
519                     return;
520                 }
521             }
522
523
524             /*
525              * Create a visible window before loading
526              */
527             if (XcosTab.get(diag) == null) {
528                 XcosTab.restore(diag);
529
530             }
531             if (openThePalette) {
532                 PaletteManager.setVisible(true);
533             }
534
535             /*
536              * Load the file
537              */
538             diag.transformAndLoadFile(controller, file);
539         }
540     }
541
542     /**
543      * Log a loading error
544      *
545      * @param lastError
546      *            the error description
547      */
548     public void setLastError(String error) {
549         this.lastError = error;
550     }
551
552     /**
553      * @return the Xcos view
554      */
555     public XcosView getXcosView() {
556         return view;
557     }
558
559     /**
560      * @return the Browser view
561      */
562     public BrowserView getBrowser() {
563         if (browser == null) {
564             browser = new BrowserView();
565             JavaController.register_view(BrowserView.class.getSimpleName(), browser);
566         }
567         return browser;
568     }
569
570     /**
571      * Clear the browser state and unregister the current view.
572      */
573     public void clearBrowser() {
574         if (browser != null) {
575             JavaController.unregister_view(browser);
576             browser = null;
577         }
578     }
579
580     /**
581      * Get an unmodifiable view of the diagrams for an UID
582      *
583      * @param root
584      *            the root diagram
585      * @return the diagram collection
586      */
587     public Collection<XcosDiagram> getDiagrams(final ScicosObjectOwner root) {
588         final Collection<XcosDiagram> diags = diagrams.get(root);
589         if (diags == null) {
590             return null;
591         }
592         return Collections.unmodifiableCollection(diags);
593     }
594
595     /**
596      * Add a diagram to the diagram list for a file. Be sure to set the right opened status on the diagram before calling this method.
597      *
598      * @param root
599      *            the root diagram
600      * @param diag
601      *            the diag
602      */
603     public void addDiagram(final ScicosObjectOwner root, final XcosDiagram diag) {
604         if (onDiagramIteration) {
605             throw new RuntimeException();
606         }
607         if (root == null) {
608             throw new IllegalArgumentException();
609         }
610
611         /*
612          * Create the collection if it does not exist
613          */
614         List<XcosDiagram> diags = diagrams.get(root);
615         if (diags == null) {
616             diags = createDiagramCollection();
617             diagrams.put(root, diags);
618         }
619
620         // insert the diagram
621         diags.add(diag);
622     }
623
624     /**
625      * Add a diagram to the opened  list
626      *
627      * This method manage both super-block and root diagrams.
628      * @param diag the diagram to add
629      */
630     public void addDiagram(final XcosDiagram diag) {
631         ScicosObjectOwner root = findRoot(diag);
632         addDiagram(root, diag);
633     }
634
635     /**
636      * Create a diagram collections (sorted List)
637      *
638      * @return the diagram collection
639      */
640     @SuppressWarnings("serial")
641     public List<XcosDiagram> createDiagramCollection() {
642         return new ArrayList<XcosDiagram>() {
643             @Override
644             public boolean add(XcosDiagram element) {
645                 final boolean status = super.add(element);
646                 DiagramComparator.sort(this);
647                 return status;
648             }
649
650             @Override
651             public boolean addAll(Collection<? extends XcosDiagram> c) {
652                 final boolean status = super.addAll(c);
653                 DiagramComparator.sort(this);
654                 return status;
655             }
656         };
657     }
658
659     /**
660      * Try to close the graph (popup save dialog)
661      *
662      * @param graph
663      *            the graph to close
664      * @return if we can (or not) close the graph
665      */
666     public boolean canClose(final XcosDiagram graph) {
667         boolean canClose = false;
668
669         ScicosObjectOwner root = findRoot(graph);
670
671         final boolean wasLastOpened = openedDiagrams(root).size() <= 1;
672         final boolean isModified = isModified(root);
673         if (!(wasLastOpened && isModified)) {
674             canClose = true;
675         }
676
677         if (!canClose) {
678             final AnswerOption ans = ScilabModalDialog.show(XcosTab.get(graph), XcosMessages.DIAGRAM_MODIFIED, XcosMessages.XCOS, IconType.QUESTION_ICON,
679                                      ButtonType.YES_NO_CANCEL);
680
681             switch (ans) {
682                 case YES_OPTION:
683                     canClose = diagrams.get(root).iterator().next().saveDiagram();
684                     break;
685                 case NO_OPTION:
686                     canClose = true; // can close
687                     break;
688                 default:
689                     canClose = false; // operation canceled
690                     break;
691             }
692         }
693
694         /*
695          * Update configuration before the destroy call to validate the uuid
696          */
697         if (canClose) {
698             configuration.addToRecentTabs(graph);
699             configuration.saveConfig();
700         }
701         return canClose;
702     }
703
704     /**
705      * Close a diagram.
706      *
707      * This method must be called on the EDT thread.
708      *
709      * @param graph
710      *            the diagram to close
711      */
712     public void destroy(XcosDiagram graph) {
713         ScicosObjectOwner root = findRoot(graph);
714
715         final boolean wasLastOpenedForFile = openedDiagrams(root).size() <= 1;
716         if (wasLastOpenedForFile) {
717             diagrams.remove(root);
718         } else {
719             diagrams.get(root).remove(graph);
720         }
721
722         if (openedDiagrams().size() <= 0) {
723             JavaController.end_simulation();
724         }
725     }
726
727     /**
728      * @param graph
729      *            the graph to handle
730      * @param list
731      *            the diagram to check
732      * @return diagram name for the "Are your sure ?" dialog
733      */
734     public String askForClosing(final XcosDiagram graph, final List<SwingScilabDockablePanel> list) {
735         final String msg;
736
737         if (wasLastOpened(list)) {
738             msg = TRADENAME;
739         } else {
740             msg = null;
741         }
742
743         return msg;
744     }
745
746     /**
747      * Does Xcos will close or not ?
748      *
749      * @param list
750      *            the list to be closed
751      * @return true if all files will be close on tabs close.
752      */
753     public boolean wasLastOpened(final List<SwingScilabDockablePanel> list) {
754         final HashSet<String> opened = new HashSet<String>();
755         for (XcosDiagram diag : openedDiagrams()) {
756             opened.add(diag.getGraphTab());
757         }
758
759         final HashSet<String> tabs = new HashSet<String>();
760         for (SwingScilabDockablePanel tab : list) {
761             if (tab != null) {
762                 tabs.add(tab.getPersistentId());
763             }
764         }
765
766         opened.removeAll(tabs);
767
768         return opened.isEmpty();
769     }
770
771     /**
772      * @return the external action list
773      */
774     public List<ExternalAction> getExternalActions() {
775         return externalActions;
776     }
777
778     /**
779      * Close the current xcos session.
780      *
781      * This method must be called on the EDT thread. For other use, please use the {@link #closeXcosFromScilab()} method.
782      */
783     public static synchronized void closeSession(final boolean ask) {
784         if (!SwingUtilities.isEventDispatchThread()) {
785             LOG.severe(CALLED_OUTSIDE_THE_EDT_THREAD);
786         }
787
788         /* Doesn't instantiate xcos on close operation */
789         if (sharedInstance == null) {
790             return;
791         }
792
793         /*
794          * Try to close all opened files
795          */
796         final Xcos instance = sharedInstance;
797
798         // get all tabs
799         final List<SwingScilabDockablePanel> tabs = new ArrayList<SwingScilabDockablePanel>();
800         for (final Collection<XcosDiagram> diags : instance.diagrams.values()) {
801             for (final XcosDiagram diag : diags) {
802                 final SwingScilabDockablePanel tab = XcosTab.get(diag);
803                 if (tab != null) {
804                     tabs.add(tab);
805                 }
806             }
807         }
808
809         // ask to close
810         final boolean status = ClosingOperationsManager.startClosingOperation(tabs, ask, ask);
811
812         // clear states
813         if (status) {
814             /* reset the shared instance state */
815             instance.diagrams.clear();
816
817             /* terminate any remaining simulation */
818             JavaController.end_simulation();
819
820             /* Saving modified data */
821             instance.palette.saveConfig();
822             instance.configuration.saveConfig();
823         }
824     }
825
826     /*
827      * Scilab exported methods.
828      *
829      * All the following methods must use SwingUtilities method to assert that the operations will be called on the EDT thread.
830      *
831      * @see modules/xcos/src/jni/Xcos.giws.xml
832      *
833      * @see sci_gateway/xcos_gateway.xml
834      *
835      * @see modules/xcos/sci_gateway/cpp/sci_*.cpp
836      */
837
838     /**
839      * Main entry point
840      *
841      * This method invoke Xcos operation on the EDT thread.
842      *
843      * @param file
844      *            The filename (can be null)
845      * @param diagramId
846      *            The Xcos DIAGRAM model ID (can be 0)
847      */
848     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
849     public static void xcos(final String file, final long diagramId) {
850         final Xcos instance = getInstance();
851         instance.lastError = null;
852
853         /* load scicos libraries (macros) */
854         InterpreterManagement.requestScilabExec(LOAD_XCOS_LIBS_LOAD_SCICOS);
855
856         synchronized (instance) {
857             /*
858              * Open the file
859              */
860             SwingUtilities.invokeLater(new Runnable() {
861                 @Override
862                 public void run() {
863                     // open on EDT
864                     instance.open(file, diagramId);
865                 }
866             });
867         }
868         if (instance.lastError != null && !instance.lastError.isEmpty()) {
869             throw new RuntimeException(instance.lastError);
870         }
871     }
872
873     /**
874      * Load or Save an xcos diagram without using Scilab at all.
875      *
876      * <P>
877      * This support a reduced number of format and should be mainly used to test
878      *
879      * @param file
880      *            the file
881      * @param diagramId
882      *            the diagram to load into
883      * @param export
884      *            flag used to indicate an export (true == export ; false == import)
885      * @throws Exception
886      *             on loading error
887      */
888     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
889     public static void xcosDiagramToScilab(String file, long diagramId, boolean export) throws Exception {
890         XcosFileType filetype = XcosFileType.findFileType(file);
891         if (filetype == null) {
892             throw new IllegalArgumentException("not handled filetype");
893         }
894
895         if (XcosFileType.getAvailableSaveFormats().contains(filetype)) {
896             if (export) {
897                 filetype.save(file, new XcosDiagram(new JavaController(), diagramId, Kind.DIAGRAM, ""));
898             } else {
899                 filetype.load(file, new XcosDiagram(new JavaController(), diagramId, Kind.DIAGRAM, ""));
900             }
901         } else {
902             throw new IllegalArgumentException("not handled filetype");
903         }
904     }
905
906     /**
907      * Close the current xcos session from any thread.
908      *
909      * This method invoke Xcos operation on the EDT thread. Please prefer using {@link #closeSession()} when the caller is on the EDT thread.
910      */
911     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
912     public static void closeXcosFromScilab() {
913         try {
914             SwingUtilities.invokeAndWait(new Runnable() {
915                 @Override
916                 public void run() {
917                     closeSession(false);
918                     clearInstance();
919                 }
920             });
921         } catch (final InterruptedException e) {
922             e.printStackTrace();
923         } catch (final InvocationTargetException e) {
924             e.printStackTrace();
925
926             Throwable throwable = e;
927             String firstMessage = null;
928             while (throwable != null) {
929                 firstMessage = throwable.getLocalizedMessage();
930                 throwable = throwable.getCause();
931             }
932
933             throw new RuntimeException(firstMessage, e);
934         }
935     }
936
937     /**
938      * Look in each diagram to find the block corresponding to the given uid and display a warning message.
939      *
940      * This method invoke Xcos operation on the EDT thread.
941      *
942      * @param uid
943      *            A String as UID.
944      * @param message
945      *            The message to display.
946      */
947     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
948     public static void warnCellByUID(final String[] uids, final String message) {
949         try {
950             SwingUtilities.invokeAndWait(new Runnable() {
951                 @Override
952                 public void run() {
953                     getInstance().warnCell(uids, message);
954                 }
955             });
956         } catch (final InterruptedException e) {
957             LOG.severe(e.toString());
958         } catch (final InvocationTargetException e) {
959             Throwable throwable = e;
960             String firstMessage = null;
961             while (throwable != null) {
962                 firstMessage = throwable.getLocalizedMessage();
963                 throwable = throwable.getCause();
964             }
965
966             throw new RuntimeException(firstMessage, e);
967         }
968     }
969
970     private void warnCell(final String[] uids, final String message) {
971
972         final mxCell[] cells  = new mxCell[uids.length];
973         final XcosDiagram[] diags  = new XcosDiagram[uids.length];
974
975         lookupForCells(uids, cells, diags);
976         for (int i = cells.length - 1; i >= 0; --i) {
977             mxCell cell = cells[i];
978
979             // perform the action on the last visible block
980             if (cell != null) {
981                 final XcosDiagram parent = diags[i];
982                 parent.warnCellByUID(cell.getId(), message);
983
984                 // Focus on an existing diagram
985                 SwingUtilities.invokeLater(() -> {
986                     XcosTab.get(parent).setCurrent();
987                 });
988
989                 return;
990             }
991
992         }
993     }
994
995     public void lookupForCells(final String[] uid, final mxCell[] cells, final XcosDiagram[] diags) {
996         final List<XcosDiagram> all = openedDiagrams();
997         for (int i = 0; i < uid.length; i++) {
998             final String id = uid[i];
999             diags[i] = all.stream().filter(d -> ((XcosGraphModel) d.getModel()).getCell(id) != null)
1000                        .findFirst().orElse(null);
1001             if (diags[i] != null) {
1002                 cells[i] = (mxCell) ((XcosGraphModel) diags[i].getModel()).getCell(id);
1003             }
1004         }
1005     }
1006
1007     /**
1008      * Add a menu into xcos
1009      *
1010      * @param label
1011      *            the label to use
1012      * @param command
1013      *            the callback (as a Scilab executable String)
1014      */
1015     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
1016     public static void addToolsMenu(final String label, final String command) {
1017         final ExternalAction action = new ExternalAction(null, command);
1018         action.putValue(Action.NAME, label);
1019         final Xcos instance = Xcos.getInstance();
1020
1021         /*
1022          * Store for future tabs
1023          */
1024         instance.externalActions.add(action);
1025
1026         /*
1027          * Update opened tabs
1028          */
1029         for (final XcosDiagram d : instance.openedDiagrams()) {
1030             final String uuid = d.getGraphTab();
1031             final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(uuid);
1032
1033             if (tab != null) {
1034                 final SwingScilabMenuBar bar = ((SwingScilabMenuBar) tab.getMenuBar().getAsSimpleMenuBar());
1035
1036                 final Component[] comps = bar.getComponents();
1037                 for (Component component : comps) {
1038                     if (component instanceof SwingScilabMenu) {
1039                         final SwingScilabMenu menu = (SwingScilabMenu) component;
1040
1041                         if (menu.getText() == XcosMessages.TOOLS) {
1042                             menu.add(new ExternalAction(action, d));
1043                         }
1044                     }
1045                 }
1046
1047                 // Also update the parent window toolbar
1048                 BarUpdater.updateBars(tab.getParentWindowId(), tab.getMenuBar(), tab.getToolBar(), tab.getInfoBar(), tab.getName(), tab.getWindowIcon());
1049             }
1050         }
1051     }
1052
1053     /**
1054      * Inform Xcos the simulator has just started
1055      *
1056      */
1057     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
1058     public static void xcosSimulationStarted() {
1059         SwingUtilities.invokeLater(new Runnable() {
1060             @Override
1061             public void run() {
1062                 GraphActionManager.setEnable(StopAction.class, true);
1063             }
1064         });
1065     }
1066
1067     /**
1068      * Look for the parent diagram of the cell in the diagram hierarchy.
1069      *
1070      * @param cell
1071      *            the cell to search for
1072      * @return the associated diagram
1073      */
1074     public static XcosDiagram findParent(Object cell) {
1075         final Xcos instance = getInstance();
1076
1077         for (Collection<XcosDiagram> diags : instance.diagrams.values()) {
1078             for (XcosDiagram diag : diags) {
1079                 final mxGraphModel model = (mxGraphModel) diag.getModel();
1080
1081                 // use the O(1) lookup
1082                 if (cell instanceof mxICell && model.getCell(((mxICell) cell).getId()) != null) {
1083                     return diag;
1084                 }
1085             }
1086         }
1087
1088         return null;
1089     }
1090
1091     /**
1092      * Look for the root object of the whole graph hierarchy
1093      * @param graph the graph
1094      * @return the root MVC object with Kind.DIAGRAM
1095      */
1096     public static ScicosObjectOwner findRoot(XcosDiagram graph) {
1097         return findRoot(new JavaController(), graph);
1098     }
1099
1100     /**
1101      * Look for the root object of the whole graph hierarchy
1102      * @param  controller the shared controller
1103      * @param graph the graph
1104      * @return the root MVC object with Kind.DIAGRAM
1105      */
1106     public static ScicosObjectOwner findRoot(JavaController controller, XcosDiagram graph) {
1107         ScicosObjectOwner root;
1108         if (graph.getKind() == Kind.DIAGRAM) {
1109             root = new ScicosObjectOwner(controller, graph.getUID(), graph.getKind());
1110         } else {
1111             long[] rootDiagram = new long[1];
1112             controller.getObjectProperty(graph.getUID(), graph.getKind(), ObjectProperties.PARENT_DIAGRAM, rootDiagram);
1113             root = new ScicosObjectOwner(controller, rootDiagram[0], Kind.DIAGRAM);
1114         }
1115
1116         return root;
1117
1118     }
1119
1120     /*
1121      * @see org.scilab.modules.gui.tabfactory.AbstractScilabTabFactory
1122      */
1123     public static class XcosTabFactory extends AbstractScilabTabFactory {
1124
1125         /*
1126          * Cache
1127          */
1128         private DocumentType cachedDocumentType;
1129
1130         /**
1131          * Default constructor
1132          */
1133         public XcosTabFactory() {
1134             this(true);
1135         }
1136
1137         private XcosTabFactory(boolean instanciateXcos) {
1138             if (instanciateXcos) {
1139                 getInstance(this);
1140             }
1141         }
1142
1143         /**
1144          * Create/restore a tab for a given uuid
1145          *
1146          * @param uuid
1147          *            the specific uuid
1148          * @return the tab instance
1149          */
1150         @Override
1151         public synchronized SwingScilabDockablePanel getTab(final String uuid) {
1152             if (uuid == null) {
1153                 return null;
1154             }
1155
1156             SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(uuid);
1157
1158             // Palette manager restore
1159             if (tab == null) {
1160                 if (PaletteManagerView.DEFAULT_TAB_UUID.equals(uuid)) {
1161                     PaletteManagerView.restore(null, false);
1162                     tab = PaletteManagerView.get();
1163                 }
1164             }
1165
1166             // diagram (tab or viewport) restore
1167             if (tab == null) {
1168                 cache(uuid);
1169                 if (cachedDocumentType == null) {
1170                     return null;
1171                 }
1172
1173                 final boolean isTab = uuid.equals(cachedDocumentType.getUuid());
1174                 final boolean isViewport = uuid.equals(cachedDocumentType.getViewport());
1175
1176                 final XcosDiagram graph = getDiagram(isTab, isViewport);
1177                 if (graph != null && isTab) {
1178                     XcosTab.restore(graph, false);
1179                     graph.fireEvent(new mxEventObject(mxEvent.ROOT));
1180                     tab = XcosTab.get(graph);
1181                 } else if (graph != null && isViewport) {
1182                     ViewPortTab.restore(graph, false);
1183                     tab = ViewPortTab.get(graph);
1184
1185                     ClosingOperationsManager.addDependency(XcosTab.get(graph), tab);
1186                     WindowsConfigurationManager.makeDependency(graph.getGraphTab(), tab.getPersistentId());
1187                 } else {
1188                     return null;
1189                 }
1190             }
1191
1192             WindowsConfigurationManager.restorationFinished(tab);
1193             ScilabTabFactory.getInstance().addToCache(tab);
1194
1195             return tab;
1196         }
1197
1198         private XcosDiagram getDiagram(boolean isTab, boolean isViewport) {
1199             final Xcos instance = getInstance();
1200             XcosDiagram graph = null;
1201
1202             if (isTab) {
1203                 // load a new diagram
1204                 graph = getInstance().configuration.loadDiagram(cachedDocumentType);
1205             } else if (isViewport) {
1206                 // get the cached diagram
1207                 final File f = instance.configuration.getFile(cachedDocumentType);
1208                 final ScicosObjectOwner root = getInstance().openedDiagram(f);
1209
1210                 Collection<XcosDiagram> diags = instance.diagrams.getOrDefault(root, Collections.emptyList());
1211                 for (XcosDiagram d : diags) {
1212                     final String id = d.getGraphTab();
1213                     if (id != null && id.equals(cachedDocumentType.getUuid())) {
1214                         graph = d;
1215                         break;
1216                     }
1217                 }
1218             }
1219
1220             return graph;
1221         }
1222
1223         @Override
1224         public synchronized boolean isAValidUUID(String uuid) {
1225             // check the Palette manager view (static uuid)
1226             if (PaletteManagerView.DEFAULT_TAB_UUID.equals(uuid)) {
1227                 return true;
1228             }
1229
1230             /*
1231              * Cache and check against cache to ease next getTab(uuid) call
1232              */
1233             cache(uuid);
1234             return cachedDocumentType != null;
1235         }
1236
1237         /**
1238          * Cache the {@link DocumentType} for the specific uuid
1239          *
1240          * @param uuid
1241          *            the uuid
1242          */
1243         private void cache(String uuid) {
1244             /*
1245              * Handle a non null cache
1246              */
1247             if (cachedDocumentType != null) {
1248                 final boolean isTab = uuid.equals(cachedDocumentType.getUuid());
1249                 final boolean isViewport = uuid.equals(cachedDocumentType.getViewport());
1250
1251                 if (isTab || isViewport) {
1252                     return;
1253                 } else {
1254                     cachedDocumentType = null;
1255                 }
1256             }
1257
1258             /*
1259              * Invalid cache, look for the right one
1260              */
1261             final ConfigurationManager config = getInstance().configuration;
1262             final List<DocumentType> docs = config.getSettings().getTab();
1263             for (DocumentType d : docs) {
1264                 final boolean isTab = uuid.equals(d.getUuid());
1265                 final boolean isViewport = uuid.equals(d.getViewport());
1266
1267                 if (isTab || isViewport) {
1268                     cachedDocumentType = d;
1269                     break;
1270                 }
1271             }
1272         }
1273
1274         @Override
1275         public String getPackage() {
1276             return TRADENAME;
1277         }
1278
1279         @Override
1280         public String getClassName() {
1281             return XcosTabFactory.class.getName();
1282         }
1283
1284         @Override
1285         public String getApplication() {
1286             return TRADENAME;
1287         }
1288     }
1289 }
1290 // CSON: ClassDataAbstractionCoupling
1291 // CSON: ClassFanOutComplexity