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