Fix weird and not reproductible exception on Scilab close
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / utils / WindowsConfigurationManager.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2011 - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.gui.utils;
14
15 import java.awt.Component;
16 import java.awt.Dimension;
17 import java.io.File;
18 import java.lang.reflect.InvocationTargetException;
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.LinkedHashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.UUID;
28
29 import javax.swing.SwingUtilities;
30 import javax.xml.xpath.XPath;
31 import javax.xml.xpath.XPathConstants;
32 import javax.xml.xpath.XPathFactory;
33
34 import org.flexdock.docking.Dockable;
35 import org.flexdock.docking.DockingManager;
36 import org.flexdock.docking.activation.ActiveDockableTracker;
37 import org.flexdock.docking.state.LayoutNode;
38 import org.flexdock.perspective.persist.xml.LayoutNodeSerializer;
39 import org.flexdock.perspective.persist.xml.PersistenceConstants;
40 import org.flexdock.util.SwingUtility;
41 import org.scilab.modules.commons.ScilabCommons;
42 import org.scilab.modules.commons.ScilabCommonsUtils;
43 import org.scilab.modules.commons.ScilabConstants;
44 import org.scilab.modules.commons.xml.ScilabXMLUtilities;
45 import org.scilab.modules.commons.xml.XConfiguration;
46 import org.scilab.modules.commons.xml.XConfigurationEvent;
47 import org.scilab.modules.commons.xml.XConfigurationListener;
48 import org.scilab.modules.gui.bridge.tab.SwingScilabTab;
49 import org.scilab.modules.gui.bridge.window.SwingScilabWindow;
50 import org.scilab.modules.gui.console.ScilabConsole;
51 import org.scilab.modules.gui.tab.Tab;
52 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
53 import org.scilab.modules.gui.window.ScilabWindow;
54 import org.scilab.modules.gui.window.Window;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57 import org.w3c.dom.NodeList;
58
59 /**
60  *
61  * Save the windows properties.
62  *
63  * @author Calixte DENIZET
64  */
65 public class WindowsConfigurationManager implements XConfigurationListener {
66
67     private static final int DEFAULTX = 0;
68     private static final int DEFAULTY = 0;
69     private static final int DEFAULTHEIGHT = 500;
70     private static final int DEFAULTWIDTH = 500;
71
72     private static final String LAYOUT_PATH = "//general/desktop-layout/body/layouts";
73
74     private static final String SCI = "SCI";
75     private static final String WINDOWS_CONFIG_FILE = System.getenv(SCI) + "/modules/gui/etc/windowsConfiguration.xml";
76     private static final String DEFAULT_WINDOWS_CONFIG_FILE = System.getenv(SCI) + "/modules/gui/etc/integratedConfiguration.xml";
77     private static final String NULLUUID = new UUID(0L, 0L).toString();
78     private static final Map<SwingScilabTab, EndedRestoration> endedRestoration = new HashMap<SwingScilabTab, EndedRestoration>();
79     private static final List<String> alreadyRestoredWindows = new ArrayList<String>();
80     private static final Map<String, Object> defaultWinAttributes = new HashMap<String, Object>();
81     private static final List<String> currentlyRestored = new ArrayList<String>();
82
83
84     private static String USER_WINDOWS_CONFIG_FILE = ScilabConstants.SCIHOME.toString() + "/windowsConfiguration.xml";
85
86     private static boolean oneTry;
87     private static Document doc;
88
89     private static boolean mustInvalidate;
90     private static boolean mustSave = true;
91
92     static {
93         try {
94             if (ScilabConstants.SCIHOME != null && ScilabConstants.SCIHOME.canRead() && ScilabConstants.SCIHOME.canWrite()) {
95                 USER_WINDOWS_CONFIG_FILE = ScilabConstants.SCIHOME.toString() + "/windowsConfiguration.xml";
96             } else {
97                 USER_WINDOWS_CONFIG_FILE = DEFAULT_WINDOWS_CONFIG_FILE;
98                 mustSave = false;
99             }
100
101             new WindowsConfigurationManager();
102             Runnable runnable = new Runnable() {
103                 public void run() {
104                     if (mustInvalidate) {
105                         File f = new File(USER_WINDOWS_CONFIG_FILE);
106                         if (f.exists() && f.isFile()) {
107                             f.delete();
108                         }
109                     }
110                 }
111             };
112
113             Class scilab = ClassLoader.getSystemClassLoader().loadClass("org.scilab.modules.core.Scilab");
114             Method registerFinalHook = scilab.getDeclaredMethod("registerFinalHook", Runnable.class);
115             registerFinalHook.invoke(null, runnable);
116
117
118             defaultWinAttributes.put("x", new Integer(DEFAULTX));
119             defaultWinAttributes.put("y", new Integer(DEFAULTY));
120             defaultWinAttributes.put("height", new Integer(DEFAULTHEIGHT));
121             defaultWinAttributes.put("width", new Integer(DEFAULTWIDTH));
122             defaultWinAttributes.put("state", new Integer(SwingScilabWindow.NORMAL));
123             /*
124               Uncomment this code for debugging focus issues
125
126               java.awt.Toolkit.getDefaultToolkit().addAWTEventListener(new java.awt.event.AWTEventListener() {
127               public void eventDispatched(java.awt.AWTEvent e) {
128               System.out.println(e);
129               }
130               }, java.awt.AWTEvent.FOCUS_EVENT_MASK);*/
131         } catch (Exception e) {
132             System.err.println(e);
133         }
134     }
135
136     private WindowsConfigurationManager() {
137         XConfiguration.addXConfigurationListener(this);
138     }
139
140     public void configurationChanged(XConfigurationEvent e) {
141         if (e.getModifiedPaths().contains(LAYOUT_PATH)) {
142             mustInvalidate = true;
143         }
144     }
145
146     public static String getLayoutFilePath() {
147         try {
148             Document doc = XConfiguration.getXConfigurationDocument();
149             if (doc != null) {
150                 XPath xp = XPathFactory.newInstance().newXPath();
151                 NodeList nodes = (NodeList) xp.compile(LAYOUT_PATH + "/layout[@id=../@id]/@path").evaluate(doc, XPathConstants.NODESET);
152                 if (nodes != null && nodes.getLength() > 0) {
153                     return nodes.item(0).getNodeValue().replace("$SCI", System.getenv(SCI));
154                 }
155             }
156         } catch (Exception e) { }
157
158         return WINDOWS_CONFIG_FILE;
159     }
160
161     /**
162      * Create a copy of windows configuration file in the user directory
163      */
164     public static void createUserCopy() {
165         if (isCopyNeeded() && mustSave) {
166             ScilabCommonsUtils.copyFile(new File(getLayoutFilePath()), new File(USER_WINDOWS_CONFIG_FILE));
167             doc = null;
168         }
169     }
170
171     /**
172      * Read the file to modify
173      */
174     private static void readDocument() {
175         if (doc == null) {
176             createUserCopy();
177             doc = ScilabXMLUtilities.readDocument(USER_WINDOWS_CONFIG_FILE);
178         }
179
180         if (doc == null && !oneTry) {
181             System.err.println("Try to reload the default configuration file.");
182             File f = new File(USER_WINDOWS_CONFIG_FILE);
183             if (f.exists() && f.isFile()) {
184                 f.delete();
185             }
186             oneTry = true;
187             readDocument();
188         } else if (doc == null && oneTry) {
189             System.err.println("Serious problem to copy and parse the configuration file.");
190             System.err.println("Please check if you have the rights to write the file: " + USER_WINDOWS_CONFIG_FILE);
191             System.err.println("If the previous file exists, please check if it is a valid XML");
192             System.err.println("and if yes, please report a bug: http://bugzilla.scilab.org");
193         }
194     }
195
196     /**
197      * Write the document
198      */
199     private static void writeDocument() {
200         if (mustSave) {
201             ScilabXMLUtilities.writeDocument(doc, USER_WINDOWS_CONFIG_FILE);
202         }
203     }
204
205     /**
206      * @return true if a copy is needed
207      */
208     private static final boolean isCopyNeeded() {
209         return !new File(USER_WINDOWS_CONFIG_FILE).exists();
210     }
211
212     /**
213      * Register an EndedRestoration, op.finish() will be executed when the tab restoration will be finished.
214      * @param tab the associated tab
215      * @param ended the closing operation
216      */
217     public static void registerEndedRestoration(SwingScilabTab tab, EndedRestoration ended) {
218         endedRestoration.put(tab, ended);
219     }
220
221     /**
222      * Register an EndedRestoration, op.finish() will be executed when the tab restoration will be finished.
223      * @param tab the associated tab
224      * @param ended the closing operation
225      */
226     public static void registerEndedRestoration(Tab tab, EndedRestoration ended) {
227         registerEndedRestoration((SwingScilabTab) tab.getAsSimpleTab(), ended);
228     }
229
230     /**
231      * Create a new node with parent element
232      * @param parent the parent element
233      * @param nodeName the node name
234      * @param attr an array containing attribute name followed by its value: "attr1", 1, "attr2", true, ...
235      * @return the created element
236      */
237     public static Element createNode(Element parent, String nodeName, Object[] attr) {
238         readDocument();
239         for (int i = 0; i < attr.length; i += 2) {
240             if (attr[i].equals("uuid")) {
241                 removeNode(parent, nodeName, (String) attr[i + 1]);
242             }
243         }
244
245         return ScilabXMLUtilities.createNode(doc, parent, nodeName, attr);
246     }
247
248     /**
249      * Save the window properties
250      * @param window the window
251      */
252     public static void saveWindowProperties(SwingScilabWindow window) {
253         readDocument();
254
255         Element root = doc.getDocumentElement();
256         Element win = createNode(root, "Window", new Object[] {"uuid", window.getUUID(),
257                                  "x", (int) window.getLastPosition().getX(),
258                                  "y", (int) window.getLastPosition().getY(),
259                                  "width", (int) window.getLastDimension().getWidth(),
260                                  "height", (int) window.getLastDimension().getHeight(),
261                                  "state", window.getExtendedState()
262                                                               });
263         LayoutNode layoutNode = window.getDockingPort().exportLayout();
264         LayoutNodeSerializer serializer = new LayoutNodeSerializer();
265         win.appendChild(serializer.serialize(doc, layoutNode));
266
267         for (Dockable dockable : (Set<Dockable>) window.getDockingPort().getDockables()) {
268             saveTabProperties((SwingScilabTab) dockable, false);
269         }
270
271         writeDocument();
272     }
273
274     /**
275      * Create a window according to the uuid.
276      *
277      * This method can be used to create a reference windows.
278      *
279      * @param uuid
280      *            the reference uuid
281      * @param preserveUUID
282      *            if true the uuid will be used on the new windows, generate a
283      *            new uuid otherwise
284      * @return the window
285      */
286     public static SwingScilabWindow createWindow(final String uuid, final boolean preserveUUID) {
287         readDocument();
288
289         final Element root = doc.getDocumentElement();
290         final boolean nullUUID = uuid.equals(NULLUUID);
291         final Map<String, Object> attrs = new HashMap<String, Object>();
292         Element win = null;
293         boolean containsX = true;
294
295         if (!nullUUID) {
296             win = getElementWithUUID(root, "Window", uuid);
297             if (win == null) {
298                 return null;
299             }
300
301             containsX = !win.getAttribute("x").equals("");
302
303             if (containsX) {
304                 attrs.put("x", int.class);
305                 attrs.put("y", int.class);
306             }
307
308             attrs.put("height", int.class);
309             attrs.put("width", int.class);
310             attrs.put("state", int.class);
311             ScilabXMLUtilities.readNodeAttributes(win, attrs);
312         } else {
313             attrs.putAll(defaultWinAttributes);
314         }
315
316         SwingScilabWindow window = new SwingScilabWindow();
317
318         final String localUUID;
319         if (preserveUUID) {
320             localUUID = uuid;
321         } else {
322             localUUID = UUID.randomUUID().toString();
323         }
324         window.setUUID(localUUID);
325
326         if (containsX) {
327             window.setLocation(((Integer) attrs.get("x")).intValue(), ((Integer) attrs.get("y")).intValue());
328         }
329
330         window.setSize(((Integer) attrs.get("width")).intValue(), ((Integer) attrs.get("height")).intValue());
331         window.setExtendedState(((Integer) attrs.get("state")).intValue());
332
333         return window;
334     }
335
336     /**
337      * Restore a window with a given uuid
338      *
339      * @param uuid
340      *            the uuid
341      * @param restoreTab
342      *            if true the tab is restored too
343      * @return the corresponding window
344      */
345     public static SwingScilabWindow restoreWindow(String uuid, String defaultTabUuid, boolean restoreTab, boolean requestFocus) {
346         readDocument();
347
348         final boolean nullUUID = uuid.equals(NULLUUID);
349
350         // create the window and preserve the uuid if not null
351         final SwingScilabWindow window = createWindow(uuid, !nullUUID);
352         if (window == null) {
353             return null;
354         }
355
356         if (restoreTab) {
357             if (!nullUUID) {
358                 final LayoutNodeSerializer serializer = new LayoutNodeSerializer();
359                 final Element dockingPort = getDockingPort(uuid);
360                 LayoutNode layoutNode = (LayoutNode) serializer.deserialize(dockingPort);
361                 window.getDockingPort().importLayout(layoutNode);
362             } else if (defaultTabUuid != null && !defaultTabUuid.isEmpty()) {
363                 SwingScilabTab defaultTab = ScilabTabFactory.getInstance().getTab(defaultTabUuid);
364                 defaultTab.setParentWindowId(window.getId());
365                 DockingManager.dock(defaultTab, window.getDockingPort());
366             }
367
368             for (SwingScilabTab tab : (Set<SwingScilabTab>) window.getDockingPort().getDockables()) {
369                 tab.setParentWindowId(window.getId());
370             }
371
372             SwingScilabTab[] tabs = new SwingScilabTab[window.getNbDockedObjects()];
373             tabs = ((Set<SwingScilabTab>) window.getDockingPort().getDockables()).toArray(tabs);
374
375             // Be sur that the main tab will have the focus.
376             // Get the elder tab and activate it
377             final SwingScilabTab mainTab = ClosingOperationsManager.getElderTab(new ArrayList(Arrays.asList(tabs)));
378             BarUpdater.updateBars(mainTab.getParentWindowId(), mainTab.getMenuBar(), mainTab.getToolBar(), mainTab.getInfoBar(), mainTab.getName(), mainTab.getWindowIcon());
379
380             if (!ScilabConsole.isExistingConsole() && tabs.length == 1 && tabs[0].getPersistentId().equals(NULLUUID)) {
381                 // null uuid is reserved to the console and in NW mode, there is no console.
382                 return null;
383             }
384
385             for (SwingScilabTab tab : tabs) {
386                 // each tab has now a window so it can be useful for the tab to set an icon window or to center a dialog...
387                 EndedRestoration ended = endedRestoration.get(tab);
388                 if (ended != null) {
389                     ended.finish();
390                     endedRestoration.remove(ended);
391                 }
392             }
393
394             if (tabs.length == 1) {
395                 // we remove undock and close buttons when there is only one View in the DockingPort
396                 SwingScilabTab.removeActions(tabs[0]);
397             } else {
398                 // we add undock and close buttons
399                 for (SwingScilabTab tab : tabs) {
400                     SwingScilabTab.addActions(tab);
401                 }
402             }
403
404             window.setVisible(true);
405
406             if (requestFocus) {
407                 SwingUtilities.invokeLater(new Runnable() {
408                     @Override
409                     public void run() {
410                         final Thread t = new Thread(new Runnable() {
411                             @Override
412                             public void run() {
413                                 synchronized (currentlyRestored) {
414                                     while (currentlyRestored.size() > 0) {
415                                         try {
416                                             currentlyRestored.wait();
417                                         } catch (InterruptedException e) {
418                                             e.printStackTrace();
419                                         }
420                                     }
421                                 }
422
423                                 window.toFront();
424                                 mainTab.requestFocusInWindow();
425                                 while (!mainTab.hasFocus()) {
426                                     Thread.yield();
427                                     mainTab.requestFocusInWindow();
428                                 }
429
430                                 ActiveDockableTracker.requestDockableActivation(mainTab);
431                             }
432                         });
433                         t.start();
434                     }
435                 });
436             }
437         }
438
439         return window;
440     }
441
442     private static final Element getDockingPort(final String winUUID) {
443         readDocument();
444
445         final Element root = doc.getDocumentElement();
446         final Element win = getElementWithUUID(root, "Window", winUUID);
447         if (win == null) {
448             return null;
449         }
450
451         final NodeList children = win.getElementsByTagName(PersistenceConstants.DOCKING_PORT_NODE_ELEMENT_NAME);
452         return (Element) children.item(0);
453     }
454
455
456     /**
457      * Must be called when the restoration is finished
458      * @param tab the tab
459      */
460     public static final void restorationFinished(SwingScilabTab tab) {
461         synchronized (currentlyRestored) {
462             currentlyRestored.remove(tab.getPersistentId());
463
464             // notify after remove
465             currentlyRestored.notify();
466         }
467     }
468
469     /**
470      * Remove a window from the already restored windows
471      * @param uuid the win uuid
472      */
473     public static final void removeWin(String uuid) {
474         alreadyRestoredWindows.remove(uuid);
475     }
476
477     /**
478      * Find all the dependencies of the given tab. The returned list contains parent before its children.
479      * @param uuid the tab's uuid
480      * @return a list of the elements with the given uuid
481      */
482     public static final Set<Element> getTabDependencies(String uuid) {
483         readDocument();
484         Element root = doc.getDocumentElement();
485
486         // Children
487         List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "depends", uuid);
488
489         Set<Element> list = new LinkedHashSet<Element>();
490         Element el = getElementWithUUID(doc.getDocumentElement(), uuid);
491         if (el != null) {
492             //We add the parent
493             list.add(el);
494             // We add the children and their own children
495             for (Element e : elements) {
496                 list.addAll(getTabDependencies(e.getAttribute("uuid")));
497             }
498         }
499
500         return list;
501     }
502
503     /**
504      * Create all the tabs depending of the tab with the given uuid.
505      * The creation will respect the convention: parent before children.
506      * @param uuid the tab uuid
507      * @return the list of all the uuids to restore
508      */
509     public static final Set<Element> createDescendantTabs(String uuid) {
510         Set<Element> list = getTabDependencies(uuid);
511         Dimension nullDims = new Dimension(0, 0);
512         for (Element e : list) {
513             // All the tabs created in the factory will be cached so when Flexdock will restore the docking
514             // it will use the same tab as created here.
515             ScilabTabFactory factory = ScilabTabFactory.getInstance();
516             factory.addTabFactory(e.getAttribute("load"), e.getAttribute("factory"));
517             currentlyRestored.add(e.getAttribute("uuid"));
518             SwingScilabTab tab = factory.getTab(e.getAttribute("uuid"));
519             if (!e.getAttribute("width").isEmpty() && !e.getAttribute("height").isEmpty()) {
520                 tab.setMinimumSize(nullDims);
521                 tab.setPreferredSize(new Dimension(Integer.parseInt(e.getAttribute("width")), Integer.parseInt(e.getAttribute("width"))));
522             }
523         }
524
525         return list;
526     }
527
528     /**
529      * Useful for the following case: you have 3 tabs, A, B and C, C depends of B, A and C are docked in the same window.
530      * You want to restore A, the window containing A needs to restore C too (and finally B).
531      * There is no direct dependency between A and C but the fact that they're in the same dockport
532      * implies a dependency.
533      * @param elems the elems corresponding to the tabs to restore
534      * @return tabs to restore
535      */
536     private static final Set<Element> createAdjacentTabs(Set<Element> elems) {
537         readDocument();
538
539         Element root = doc.getDocumentElement();
540         boolean jobFinished = true;
541         Set<Element> toAdd = new LinkedHashSet<Element>();
542         for (Element e : elems) {
543             String winuuid = e.getAttribute("winuuid");
544             if (!winuuid.isEmpty() && !winuuid.equals(NULLUUID)) {
545                 List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "winuuid", winuuid);
546                 elements.removeAll(elems);
547                 jobFinished = jobFinished && elements.size() == 0;
548                 toAdd.addAll(elements);
549             }
550         }
551
552         if (jobFinished) {
553             return elems;
554         }
555
556         for (Element ee : toAdd) {
557             elems.addAll(createDescendantTabs(getElderParent(ee).getAttribute("uuid")));
558         }
559
560
561         return createAdjacentTabs(elems);
562     }
563
564     /**
565      * @param e the element
566      * @return the elder parent of this element (elder for the attribute "depends")
567      */
568     private static final Element getElderParent(Element e) {
569         readDocument();
570
571         Element root = doc.getDocumentElement();
572         String dep = e.getAttribute("depends");
573         if (!dep.isEmpty()) {
574             return getElderParent(ScilabXMLUtilities.getElementsWithAttributeEquals(root, "uuid", dep).get(0));
575         }
576
577         return e;
578     }
579
580     /**
581      * Starts the restoration of the tab with the given uuid
582      * @param uuid the tab uuid to restore
583      */
584     private static final void startRestoration(String uuid) {
585         Set<Element> list = createDescendantTabs(uuid);
586         list = createAdjacentTabs(list);
587         List<String> wins = new ArrayList<String>();
588         List<String> tabsWithoutWin = new ArrayList<String>();
589         for (Element e : list) {
590             String winuuid = e.getAttribute("winuuid");
591             if (winuuid.equals(NULLUUID) || getElementWithUUID(winuuid) == null || !isDockableIdExisting(winuuid, e.getAttribute("uuid"))) {
592                 tabsWithoutWin.add(e.getAttribute("uuid"));
593             } else if (!wins.contains(winuuid)) {
594                 wins.add(winuuid);
595             }
596         }
597
598         boolean requestFocus = true;
599
600         if (wins.contains(NULLUUID)) {
601             wins.remove(NULLUUID);
602             wins.add(NULLUUID);
603         }
604
605         for (String winuuid : wins) {
606             if (!alreadyRestoredWindows.contains(winuuid)) {
607                 restoreWindow(winuuid, uuid, true, requestFocus);
608                 alreadyRestoredWindows.add(winuuid);
609                 if (requestFocus) {
610                     requestFocus = false;
611                 }
612             }
613         }
614
615         for (String u : tabsWithoutWin) {
616             SwingScilabWindow window = restoreWindow(NULLUUID, u, true, requestFocus);
617             alreadyRestoredWindows.add(window.getUUID());
618             if (requestFocus) {
619                 requestFocus = false;
620             }
621         }
622     }
623
624     /**
625      * Search a node (child of root) with name nodeName and with a given uuid
626      * @param root the root element
627      * @param nodeName the node name
628      * @param uuid the uuid
629      * @return the corresponding element or null if it does not exist
630      */
631     public static final Element getElementWithUUID(Element root, String nodeName, String uuid) {
632         if (uuid == null || uuid.isEmpty()) {
633             return null;
634         }
635         NodeList list = root.getElementsByTagName(nodeName);
636         int length = getNodeListLength(list);
637         for (int i = 0; i < length; i++) {
638             Element elem = (Element) list.item(i);
639             if (elem.getAttribute("uuid").equals(uuid)) {
640                 return elem;
641             }
642         }
643
644         return null;
645     }
646
647     /**
648      * Search a node with a given uuid
649      * @param uuid the uuid
650      * @return the corresponding element or null if it does not exist
651      */
652     public static final Element getElementWithUUID(String uuid) {
653         readDocument();
654
655         return getElementWithUUID(doc.getDocumentElement(), uuid);
656     }
657
658     /**
659      * Search a node (child of root) with name nodeName and with a given uuid
660      * @param root the root element
661      * @param uuid the uuid
662      * @return the corresponding element or null if it does not exist
663      */
664     public static final Element getElementWithUUID(Element root, String uuid) {
665         if (uuid == null || uuid.isEmpty()) {
666             return null;
667         }
668         List<Element> list = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "uuid", uuid);
669         if (list.size() != 0) {
670             return list.get(0);
671         }
672
673         return null;
674     }
675
676     /**
677      * Check if there is a window which has a dockableID equals to the given uuid
678      * @param winuuid the uuid of the window
679      * @param uuid the uuid to test
680      * @return true if a dockableId exists
681      */
682     public static final boolean isDockableIdExisting(String winuuid, String uuid) {
683         if (winuuid == null || winuuid.isEmpty() || uuid == null || uuid.isEmpty()) {
684             return false;
685         }
686
687         Element win = getElementWithUUID(winuuid);
688         if (win != null) {
689             List<Element> list = ScilabXMLUtilities.getElementsWithAttributeEquals(win, "dockableId", uuid);
690             if (list.size() != 0) {
691                 return true;
692             }
693         }
694
695         return false;
696     }
697
698     /**
699      * Validate all the windows in checking if all the dockable nodes are ok.
700      * If a split node contains an invalid uuid, then the invalid dockable is removed
701      * and the split node is replaced by the valid dockable.
702      */
703     private static final void validateWindows() {
704         // We remove all the blanks and carriage return
705         try {
706             XPath xp = XPathFactory.newInstance().newXPath();
707             NodeList nodes = (NodeList) xp.compile("//text()").evaluate(doc, XPathConstants.NODESET);
708             for (int i = 0; i < nodes.getLength(); i++) {
709                 nodes.item(i).getParentNode().removeChild(nodes.item(i));
710             }
711         } catch (Exception e) {
712             e.printStackTrace();
713         }
714
715         Element root = doc.getDocumentElement();
716         NodeList list = root.getElementsByTagName("Window");
717
718         int length = getNodeListLength(list);
719         List<Element> elems = new ArrayList<Element>(length);
720         for (int i = 0; i < length; i++) {
721             elems.add((Element) list.item(i));
722         }
723
724         for (Element e : elems) {
725             validateWindow(e.getAttribute("uuid"));
726         }
727     }
728
729     /**
730      * Validate a window element.
731      * The window element in the DOM is eventually replaced by a valid one.
732      * @param winuuid the window uuid
733      */
734     private static final void validateWindow(final String winuuid) {
735         if (winuuid == null || winuuid.isEmpty()) {
736             return;
737         }
738
739         Element win = getElementWithUUID(winuuid);
740         if (win == null) {
741             return;
742         }
743
744         Element dp = (Element) win.getFirstChild();
745         if (dp == null) {
746             win.getParentNode().removeChild(win);
747             return;
748         }
749
750         Element e = validateDockingPortNode(winuuid, dp);
751         if (e == null) {
752             win.removeChild(dp);
753             return;
754         }
755
756         win.removeChild(dp);
757         win.appendChild(e);
758     }
759
760     /**
761      * Validate a dockingport node element.
762      * @param winuuid the window uuid
763      * @param e the element to validate
764      * @return a valid element
765      */
766     private static final Element validateDockingPortNode(final String winuuid, final Element e) {
767         Element ee = (Element) e.getFirstChild();
768         if (ee == null) {
769             return null;
770         }
771
772         if (ee.getTagName().equals("DockableNode")) {
773             ee = validateDockableNode(winuuid, ee);
774             if (ee == null) {
775                 return null;
776             }
777             return e;
778         } else {
779             ee = validateSplitNode(winuuid, ee);
780             if (ee == null) {
781                 return null;
782             }
783             Element eee = (Element) e.cloneNode(false);
784             eee.appendChild(ee);
785
786             return eee;
787         }
788     }
789
790     /**
791      * Validate a dockable node element.
792      * @param winuuid the window uuid
793      * @param e the element to validate
794      * @return a valid element
795      */
796     private static final Element validateDockableNode(final String winuuid, final Element e) {
797         String id = e.getAttribute("dockableId");
798         Element ee = getElementWithUUID(id);
799         if (ee == null || !ee.getAttribute("winuuid").equals(winuuid)) {
800             return null;
801         }
802
803         return e;
804     }
805
806     /**
807      * Validate a split node element.
808      * @param winuuid the window uuid
809      * @param e the element to validate
810      * @return a valid element
811      */
812     private static final Element validateSplitNode(final String winuuid, final Element e) {
813         NodeList set = e.getChildNodes();
814         Element c1 = validateDockingPortNode(winuuid, (Element) set.item(0));
815         Element c2 = validateDockingPortNode(winuuid, (Element) set.item(1));
816
817         if (c1 != null && c2 != null) {
818             Element ee = (Element) e.cloneNode(false);
819             ee.appendChild(c1);
820             ee.appendChild(c2);
821             return ee;
822         }
823
824         if (c1 == null && c2 == null) {
825             return null;
826         }
827
828         if (c1 == null) {
829             return (Element) c2.getFirstChild().cloneNode(true);
830         }
831
832         return (Element) c1.getFirstChild().cloneNode(true);
833     }
834
835     /**
836      * Remove a node with a given uuid
837      * @param parent the parent element
838      * @param nodeName the node name
839      * @param uuid the uuid
840      */
841     private static final void removeNode(Element parent, String nodeName, String uuid) {
842         if (uuid == null || uuid.isEmpty()) {
843             return;
844         }
845         Element e = getElementWithUUID(parent, nodeName, uuid);
846         if (e != null) {
847             parent.removeChild(e);
848         }
849     }
850
851     /**
852      * Remove a node with a given uuid
853      * @param nodeName the node name
854      * @param uuid the uuid
855      */
856     public static final void removeNode(String uuid) {
857         if (uuid == null || uuid.isEmpty()) {
858             return;
859         }
860         Element e = getElementWithUUID(uuid);
861         if (e != null && e.getParentNode() != null) {
862             e.getParentNode().removeChild(e);
863         }
864     }
865
866     /**
867      * Save the tab properties
868      * @param tab the tab
869      * @param nullWin if true, the winuuid will be set to 0 (the tab is not docked)
870      */
871     public static void saveTabProperties(SwingScilabTab tab, boolean nullWin) {
872         readDocument();
873
874         ScilabTabFactory factory = ScilabTabFactory.getInstance();
875         String uuid = tab.getPersistentId();
876         Element root = doc.getDocumentElement();
877
878         String app = factory.getApplication(uuid);
879         if (app.isEmpty()) {
880             return;
881         }
882
883         String winuuid;
884         if (nullWin) {
885             winuuid = NULLUUID;
886         } else {
887             winuuid = tab.getParentWindowUUID();
888         }
889
890         Dimension dim = tab.getSize();
891
892         createNode(root, app, new Object[] {"winuuid", winuuid,
893                                             "uuid", uuid,
894                                             "load", factory.getPackage(uuid),
895                                             "factory", factory.getClassName(uuid),
896                                             "width", (int) dim.getWidth(),
897                                             "height", (int) dim.getHeight()
898                                            });
899         writeDocument();
900     }
901
902     /**
903      * Clean the document in removing the useless tags
904      * and validate the different windows.
905      */
906     public static void clean() {
907         readDocument();
908
909         validateWindows();
910
911         Element root = doc.getDocumentElement();
912         NodeList list = root.getElementsByTagName("Window");
913         int len = getNodeListLength(list);
914         for (int i = 0; i < len; i++) {
915             if (list.item(i) instanceof Element) {
916                 String uuid = ((Element) list.item(i)).getAttribute("uuid");
917                 List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "winuuid", uuid);
918                 if (elements == null || elements.size() == 0) {
919                     root.removeChild(list.item(i));
920                     removeWin(uuid);
921                 }
922             }
923         }
924         writeDocument();
925     }
926
927     /**
928      * Make a dependency between two tabs
929      * @param parentUUID the parent tab uuid
930      * @param childUUID the child tab uuid
931      */
932     public static final void makeDependency(String parentUUID, String childUUID) {
933         readDocument();
934
935         Element e = getElementWithUUID(doc.getDocumentElement(), childUUID);
936         if (e != null) {
937             e.setAttribute("depends", parentUUID);
938         }
939         writeDocument();
940     }
941
942     /**
943      * Remove a dependency with the parent tab
944      * @param childUUID the child tab uuid
945      */
946     public static void removeDependency(String childUUID) {
947         readDocument();
948
949         Element e = getElementWithUUID(doc.getDocumentElement(), childUUID);
950         if (e != null) {
951             e.removeAttribute("depends");
952         }
953         writeDocument();
954     }
955
956     /**
957      * Restore an application by its uuid
958      * @param uuid the application uuid
959      * @return true if the operation succeded
960      */
961     public static boolean restoreUUID(final String uuid) {
962         readDocument();
963         clean();
964
965         Element elem = getElementWithUUID(uuid);
966         if (elem == null) {
967             return false;
968         }
969
970         if (SwingUtilities.isEventDispatchThread()) {
971             startRestoration(uuid);
972         } else {
973             try {
974                 SwingUtilities.invokeAndWait(new Runnable() {
975                     public void run() {
976                         startRestoration(uuid);
977                     }
978                 });
979             } catch (InvocationTargetException e) {
980                 System.err.println(e);
981             } catch (InterruptedException e) {
982                 System.err.println(e);
983             }
984         }
985
986         writeDocument();
987         doc = null;
988
989         return true;
990     }
991
992     /**
993      * Get the uuids of an application
994      * @param name the application anem
995      * @return the corresponding uuids
996      */
997     public static String[] getApplicationUUIDs(String name) {
998         readDocument();
999
1000         NodeList list = doc.getDocumentElement().getElementsByTagName(name);
1001         String[] uuids = new String[getNodeListLength(list)];
1002         for (int i = 0; i < uuids.length; i++) {
1003             if (list.item(i) instanceof Element) {
1004                 Element elem = (Element) list.item(i);
1005                 uuids[i] = elem.getAttribute("uuid");
1006             }
1007         }
1008
1009         return uuids;
1010     }
1011
1012     /**
1013      * Get the length of a NodeList (this is a workaround for a f... java bug)
1014      */
1015     private static final int getNodeListLength(NodeList list) {
1016         int length = 0;
1017         try {
1018             length = list.getLength();
1019         } catch (NullPointerException e) { }
1020
1021         return length;
1022     }
1023
1024
1025     /**
1026      * Inner interface used to have something to execute when the restoration is finished
1027      */
1028     public interface EndedRestoration {
1029
1030         /**
1031          * Stuff to do when the restoration is ended
1032          */
1033         public void finish();
1034     }
1035 }