You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by ju...@apache.org on 2019/05/25 00:14:58 UTC

[netbeans] branch master updated: [NETBEANS-1979] Create Mode from client code (#1135)

This is an automated email from the ASF dual-hosted git repository.

junichi11 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 3b6c4f5  [NETBEANS-1979] Create Mode from client code (#1135)
3b6c4f5 is described below

commit 3b6c4f588a5602bebcf7d5ac1e7132c88a156ad3
Author: Mark Phipps <mw...@gmail.com>
AuthorDate: Sat May 25 01:14:53 2019 +0100

    [NETBEANS-1979] Create Mode from client code (#1135)
    
    Allow any kind of Mode to be created programatically from a ModeConfig XML String so that a TopComponent may then be programatically docked into that Mode.
---
 .../src/org/netbeans/core/windows/ModeImpl.java    |  18 +-
 .../netbeans/core/windows/PersistenceHandler.java  |   2 +-
 .../netbeans/core/windows/WindowManagerImpl.java   |  64 +++++++
 .../core/windows/persistence/ModeConfig.java       |   6 +
 .../core/windows/persistence/ModeParser.java       | 133 ++++++++++++---
 .../windows/persistence/PersistenceManager.java    |  13 +-
 .../windows/persistence/WindowManagerParser.java   | 109 +++++++++---
 .../core/windows/WindowManagerModeTest.java        | 188 +++++++++++++++++++++
 .../core/windows/persistence/ModeParserTest.java   |   2 +-
 platform/openide.windows/apichanges.xml            |  17 ++
 platform/openide.windows/manifest.mf               |   2 +-
 .../src/org/openide/windows/Mode.java              |  16 ++
 .../src/org/openide/windows/ModeUtilities.java     |  63 +++++++
 .../src/org/openide/windows/WindowManager.java     |  38 +++++
 14 files changed, 617 insertions(+), 54 deletions(-)

diff --git a/platform/core.windows/src/org/netbeans/core/windows/ModeImpl.java b/platform/core.windows/src/org/netbeans/core/windows/ModeImpl.java
index 9a3ec40..2b2dc6a 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/ModeImpl.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/ModeImpl.java
@@ -32,7 +32,11 @@ import java.util.Set;
 import javax.swing.SwingUtilities;
 import java.awt.Image;
 import java.awt.Rectangle;
+import java.io.IOException;
 import java.util.Collection;
+import org.netbeans.core.windows.persistence.ModeConfig;
+import org.netbeans.core.windows.persistence.PersistenceManager;
+import org.openide.util.Exceptions;
 
 
 /** This class is an implementation of Mode interface.
@@ -40,7 +44,7 @@ import java.util.Collection;
  *
  * @author Peter Zavadsky
  */
-public final class ModeImpl implements Mode {
+public final class ModeImpl implements Mode.Xml {
 
     /** Name constant as a base for nonamed modes. */
     private static final String MODE_ANONYMOUS_NAME = "anonymousMode"; // NOI18N
@@ -544,6 +548,16 @@ public final class ModeImpl implements Mode {
         getCentral().setModeName(this, text);
     }
 
-    
+    @Override
+    public String toXml() {
+        ModeConfig config = PersistenceHandler.getDefault().getConfigFromMode(this);
+        try {
+            return PersistenceManager.getDefault().createXmlFromMode(config);
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+            return "";
+        }
+    }
+
 }
 
diff --git a/platform/core.windows/src/org/netbeans/core/windows/PersistenceHandler.java b/platform/core.windows/src/org/netbeans/core/windows/PersistenceHandler.java
index 30ed958..b2ff1c0 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/PersistenceHandler.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/PersistenceHandler.java
@@ -630,7 +630,7 @@ final public class PersistenceHandler implements PersistenceObserver {
         return wmc;
     }
 
-    private ModeConfig getConfigFromMode(ModeImpl mode) {
+    public ModeConfig getConfigFromMode(ModeImpl mode) {
         PersistenceManager pm = PersistenceManager.getDefault();
         WindowManagerImpl wm = WindowManagerImpl.getInstance();
         ModeConfig modeCfg = new ModeConfig();
diff --git a/platform/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java b/platform/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java
index 8f6faf1..82cecc4 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/WindowManagerImpl.java
@@ -23,6 +23,7 @@ import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.beans.*;
+import java.io.IOException;
 import java.net.URL;
 import java.util.*;
 import java.util.logging.Level;
@@ -31,6 +32,7 @@ import javax.swing.*;
 import org.netbeans.core.IDESettings;
 import org.netbeans.core.windows.actions.ActionUtils;
 import org.netbeans.core.windows.options.WinSysPrefs;
+import org.netbeans.core.windows.persistence.ModeConfig;
 import org.netbeans.core.windows.persistence.PersistenceManager;
 import org.netbeans.core.windows.view.dnd.TopComponentDraggable;
 import org.netbeans.core.windows.view.ui.MainWindow;
@@ -457,7 +459,61 @@ public final class WindowManagerImpl extends WindowManager implements Workspace
             System.setProperty("nb.native.filechooser", useNativeFileChooser ? "true" : "false"); //NOI18N
         }
     }
+
+    @Override
+    public Mode createModeFromXml(String xml) {
+        try {
+            ModeConfig modeConfig = PersistenceManager.getDefault().createModeFromXml(xml);
+            ModeImpl mode = createMode(modeConfig);
+            addMode(mode, modeConfig);
+            return mode;
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+            return null;
+        }
+    }
+    
+    public ModeImpl createMode(ModeConfig config) {
+        WindowManagerImpl wmi = WindowManagerImpl.getInstance();
+
+        ModeImpl res = wmi.createMode(config.name, config.kind, config.state, false, config.constraints);
+        Rectangle absBounds = config.bounds == null ? new Rectangle() : config.bounds;
+        Rectangle relBounds = config.relativeBounds == null ? new Rectangle() : config.relativeBounds;
+        Rectangle bounds = PersistenceHandler.computeBounds(false, false,
+                absBounds.x,
+                absBounds.y,
+                absBounds.width,
+                absBounds.height,
+                relBounds.x / 100.0F,
+                relBounds.y / 100.0F,
+                relBounds.width / 100.0F,
+                relBounds.height / 100.0F);
+        res.setBounds(bounds);
+        res.setFrameState(config.frameState);
+        res.setMinimized(config.minimized);
+        return res;
+    }
+    
+    @Override
+    public boolean removeMode(Mode mode) {
+        String modeName = mode.getName();
+        removeMode((ModeImpl)mode);
+        return findMode(modeName) == null;
+    }
     
+    @Override
+    public boolean updateModeConstraintsFromXml(String xml) {
+        try {
+            ModeConfig modeConfig = PersistenceManager.getDefault().createModeFromXml(xml);
+            ModeImpl mode = findModeImpl(modeConfig.name);
+            mode.setConstraints(modeConfig.constraints);
+            return true;
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+            return false;
+        }
+    }
+
     private static class WrapMode implements Mode {
         private Mode wrap;
         
@@ -875,6 +931,14 @@ public final class WindowManagerImpl extends WindowManager implements Workspace
         }
     }
     
+    private void addMode(ModeImpl mode, ModeConfig modeConfig) {
+        if (mode.getKind() == Constants.MODE_KIND_SLIDING) {
+            central.addSlidingMode(mode, null, modeConfig.side, modeConfig.slideInSizes);
+        } else {
+            central.addMode(mode, modeConfig.constraints);
+        }
+    }
+    
     /** Removes mode. */
     public void removeMode(ModeImpl mode) {
         if (mode.getKind() == Constants.MODE_KIND_SLIDING) {
diff --git a/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeConfig.java b/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeConfig.java
index 7cf9b5c..47238ac 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeConfig.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeConfig.java
@@ -22,6 +22,7 @@ package org.netbeans.core.windows.persistence;
 
 
 import java.awt.Rectangle;
+import java.util.Arrays;
 import java.util.Collection;
 import org.netbeans.core.windows.SplitConstraint;
 
@@ -212,5 +213,10 @@ public class ModeConfig {
         hash = 37 * hash + previousSelectedTopComponentID.hashCode();
         return hash;
     }
+
+    @Override
+    public String toString() {
+        return "ModeConfig{" + "name=" + name + ", otherNames=" + otherNames + ", state=" + state + ", kind=" + kind + ", side=" + side + ", constraints=" + Arrays.asList(constraints).toString() + ", bounds=" + bounds + ", relativeBounds=" + relativeBounds + ", frameState=" + frameState + ", selectedTopComponentID=" + selectedTopComponentID + ", permanent=" + permanent + ", minimized=" + minimized + ", tcRefConfigs=" + Arrays.asList(tcRefConfigs).toString() + ", slideInSizes=" + slideInS [...]
+    }
     
 }
diff --git a/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeParser.java b/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeParser.java
index 5ffc7b8..8b4c597 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeParser.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/persistence/ModeParser.java
@@ -37,6 +37,7 @@ import java.io.*;
 import java.util.*;
 import java.util.List;
 import java.util.logging.Logger;
+import org.openide.util.io.ReaderInputStream;
 
 
 /**
@@ -96,12 +97,23 @@ class ModeParser {
     
     private final Object LOCK = new Object();
     
-    public ModeParser (String name, Set maskSet) {
+    private final boolean fileObjectNameMustMatchModeName;
+
+    public static ModeParser parseFromFileObject(String name, Set maskSet) {
+        return new ModeParser(name, maskSet, true);
+    }
+    
+    public static ModeParser parseFromString(String name, Set maskSet) {
+        return new ModeParser(name, maskSet, false);
+    }
+    
+    private ModeParser (String name, Set maskSet, boolean fileObjectNameMustMatchModeName) {
         this.modeName = name;
         this.maskSet = maskSet;
+        this.fileObjectNameMustMatchModeName = fileObjectNameMustMatchModeName;
     }
     
-    /** Load mode configuration including all tcrefs. */
+    /** Load mode configuration including all tcrefs from an XML file. */
     ModeConfig load () throws IOException {
         synchronized( LOCK ) {
             //if (DEBUG) Debug.log(ModeParser.class, "load ENTER" + " mo:" + name);
@@ -127,6 +139,30 @@ class ModeParser {
         }
     }
     
+    /** Load mode configuration from an XML String. */
+    ModeConfig load (String xml) throws IOException {
+        synchronized( LOCK ) {
+            //if (DEBUG) Debug.log(ModeParser.class, "load ENTER" + " mo:" + name);
+            ModeConfig mc = new ModeConfig();
+            readProperties(mc, xml);
+            if (mc.kind == Constants.MODE_KIND_SLIDING && mc.side != null && !mc.permanent) {
+                // now we have the 4.0 anonymous mode for the slide bar. replace with the 
+                // predefined ones..
+                mc.permanent = true;
+                // well, the names are defined in core/ui.
+                // shall we at all care about the name? or is making it permanent just fine?
+    //            if (mc.side.equals(Constants.BOTTOM)) {
+    //                mc.name = "bottomSlidingSide"; //NOI18N
+    //            } else if (mc.side.equals(Constants.LEFT)) {
+    //                mc.name = "leftSlidingSide"; //NOI18N
+    //            } else if (mc.side.equals(Constants.RIGHT)) {
+    //                mc.name = "rightSlidingSide"; //NOI18N
+    //            }
+            }
+            return mc;
+        }
+    }
+    
     /** Save mode configuration including all tcrefs. */
     void save (ModeConfig mc) throws IOException {
         synchronized( LOCK ) {
@@ -137,6 +173,15 @@ class ModeParser {
         }
     }
     
+    String modeConfigXml(ModeConfig mc) throws IOException {
+        synchronized( LOCK ) {
+            PropertyHandler propertyHandler = new PropertyHandler();
+            InternalConfig internalCfg = getInternalConfig();
+            StringBuffer buff = propertyHandler.generateData(mc, internalCfg);
+            return buff.toString();
+        }
+    }
+    
     private void readProperties (ModeConfig mc) throws IOException {
         if (DEBUG) Debug.log(ModeParser.class, "readProperties ENTER" + " mo:" + getName());
         PropertyHandler propertyHandler = new PropertyHandler();
@@ -152,6 +197,21 @@ class ModeParser {
         if (DEBUG) Debug.log(ModeParser.class, "readProperties LEAVE" + " mo:" + getName());
     }
     
+    private void readProperties (ModeConfig mc, String xml) throws IOException {
+        if (DEBUG) Debug.log(ModeParser.class, "readProperties ENTER" + " mo:" + getName());
+        PropertyHandler propertyHandler = new PropertyHandler();
+        InternalConfig internalCfg = getInternalConfig();
+        internalCfg.clear();
+        propertyHandler.readData(mc, internalCfg, xml);
+        
+        /*if (DEBUG) Debug.log(ModeParser.class, "               specVersion: " + internalCfg.specVersion);
+        if (DEBUG) Debug.log(ModeParser.class, "        moduleCodeNameBase: " + internalCfg.moduleCodeNameBase);
+        if (DEBUG) Debug.log(ModeParser.class, "     moduleCodeNameRelease: " + internalCfg.moduleCodeNameRelease);
+        if (DEBUG) Debug.log(ModeParser.class, "moduleSpecificationVersion: " + internalCfg.moduleSpecificationVersion);*/
+        
+        if (DEBUG) Debug.log(ModeParser.class, "readProperties LEAVE" + " mo:" + getName());
+    }
+    
     private void readTCRefs (ModeConfig mc) throws IOException {
         if (DEBUG) Debug.log(ModeParser.class, "readTCRefs ENTER" + " mo:" + getName());
         
@@ -894,22 +954,42 @@ class ModeParser {
                 return modeConfigFO;
             }
         }
-        /** 
-         Reads mode configuration data from XML file. 
-         Data are returned in output params.
+        
+        /**
+         * Reads mode configuration data from XML file. Data are returned in
+         * output params.
          */
-        void readData (ModeConfig modeCfg, InternalConfig internalCfg)
-        throws IOException {
-            modeConfig = modeCfg;
-            internalConfig = internalCfg;
-            itemList.clear();
-            
+        void readData(ModeConfig modeCfg, InternalConfig internalCfg)
+                throws IOException {
             FileObject cfgFOInput = getConfigFOInput();
             if (cfgFOInput == null) {
                 throw new FileNotFoundException("[WinSys] Missing Mode configuration file:" // NOI18N
-                + ModeParser.this.getName());
+                        + ModeParser.this.getName());
             }
-            InputStream is = null;
+            InputStream is = cfgFOInput.getInputStream();
+            readData(modeCfg, internalCfg, is, cfgFOInput);
+        }
+        
+        /**
+         * Reads mode configuration data from XML String. Data are returned in
+         * output params.
+         */
+        void readData(ModeConfig modeCfg, InternalConfig internalCfg, String xml)
+                throws IOException {
+            InputStream is = new BufferedInputStream( new ReaderInputStream( new StringReader(xml)));
+            readData(modeCfg, internalCfg, is, xml);
+        }
+        
+        /**
+         * Reads mode configuration data from an InputStream. Data are returned
+         * in output params.
+         */
+        private void readData(ModeConfig modeCfg, InternalConfig internalCfg, InputStream is, Object source)
+                throws IOException {
+            modeConfig = modeCfg;
+            internalConfig = internalCfg;
+            itemList.clear();
+            
             try {
                 synchronized (RW_LOCK) {
                     //DUMP BEGIN
@@ -922,13 +1002,12 @@ class ModeParser {
                         if (DEBUG) Debug.log(ModeParser.class, s);
                     }*/
                     //DUMP END
-                    is = cfgFOInput.getInputStream();
                     PersistenceManager.getDefault().getXMLParser(this).parse(new InputSource(is));
                 }
             } catch (SAXException exc) {
                 // Turn into annotated IOException
                 String msg = NbBundle.getMessage(ModeParser.class,
-                                                 "EXC_ModeParse", cfgFOInput);
+                                                 "EXC_ModeParse", source);
 
                 throw (IOException) new IOException(msg).initCause(exc);
             } finally {
@@ -1049,11 +1128,11 @@ class ModeParser {
             String name = attrs.getValue("unique"); // NOI18N
             if (name != null) {
                 modeConfig.name = name;
-                if (!name.equals(ModeParser.this.getName())) {
+                if (fileObjectNameMustMatchModeName && !name.equals(ModeParser.this.getName())) {
                     PersistenceManager.LOG.log(Level.INFO,
                     "[WinSys.ModeParser.handleName]" // NOI18N
                     + " Error: Value of attribute \"unique\" of element \"name\"" // NOI18N
-                    + " and configuration file name must be the same."); // NOI18N
+                    + " and configuration file name must be the same: " + name + " != " + ModeParser.this.getName() + "."); // NOI18N
                     throw new SAXException("Invalid attribute value"); // NOI18N
                 }
             } else {
@@ -1096,13 +1175,13 @@ class ModeParser {
                     if( null != modeConfig.otherNames && !modeConfig.otherNames.isEmpty() ) {
                         PersistenceManager.LOG.log(Level.INFO,
                         "[WinSys.ModeParser.handleName]" // NOI18N
-                        + " Error: Sliding modes are not allowed to have additional names."); // NOI18N
+                        + " Error: Sliding modes are not allowed to have additional names: " + modeConfig.otherNames + "."); // NOI18N
                         throw new SAXException("Invalid attribute value"); // NOI18N
                     }
                 } else {
                     PersistenceManager.LOG.log(Level.INFO,
                     "[WinSys.ModeParser.handleKind]" // NOI18N
-                    + " Warning: Invalid value of attribute \"type\"."); // NOI18N
+                    + " Warning: Invalid value of attribute \"type\": " + type + "."); // NOI18N
                     modeConfig.kind = Constants.MODE_KIND_VIEW;
                 }
             } else {
@@ -1154,7 +1233,7 @@ class ModeParser {
             } 
             PersistenceManager.LOG.log(Level.INFO,
             "[WinSys.ModeParser.handleSlideInSize]" // NOI18N
-            + " Warning: Invalid attributes for preferred slide-in size."); // NOI18N
+            + " Warning: Invalid attributes for preferred slide-in size: tc-id=" + tcId + ", size=" + size + "."); // NOI18N
         }      
         
         private void handleState(Attributes attrs) throws SAXException {
@@ -1167,7 +1246,7 @@ class ModeParser {
                 } else {
                     PersistenceManager.LOG.log(Level.INFO,
                     "[WinSys.ModeParser.handleState]" // NOI18N
-                    + " Warning: Invalid value of attribute \"type\"" // NOI18N
+                    + " Warning: Invalid value " + type + " of attribute \"type\"" // NOI18N
                     + " of element \"state\"."); // NOI18N
                     modeConfig.state = Constants.MODE_STATE_JOINED;
                 }
@@ -1193,7 +1272,7 @@ class ModeParser {
                 } else {
                     PersistenceManager.LOG.log(Level.INFO,
                     "[WinSys.ModeParser.handleState]" // NOI18N
-                    + " Warning: Invalid value of attribute \"minimized\"" // NOI18N
+                    + " Warning: Invalid value " + minimized + " of attribute \"minimized\"" // NOI18N
                     + " of element \"state\"."); // NOI18N
                     modeConfig.minimized = false;
                 }
@@ -1215,7 +1294,7 @@ class ModeParser {
             } else {
                 PersistenceManager.LOG.log(Level.INFO,
                 "[WinSys.ModeParser.handlePath]" // NOI18N
-                + " Warning: Invalid or missing value of attribute \"orientation\"."); // NOI18N
+                + " Warning: Invalid or missing value " + s + " of attribute \"orientation\"."); // NOI18N
                 orientation = Constants.VERTICAL;
             }
             
@@ -1367,7 +1446,7 @@ class ModeParser {
                 } catch (NumberFormatException exc) {
                     PersistenceManager.LOG.log(Level.INFO,
                     "[WinSys.ModeParser.handleFrame]" // NOI18N
-                    + " Warning: Cannot read attribute \"state\"" // NOI18N
+                    + " Warning: Cannot read value " + frameState + " for attribute \"state\"" // NOI18N
                     + " of element \"frame\".", exc); // NOI18N
                     modeConfig.frameState = Frame.NORMAL;
                 }
@@ -1406,11 +1485,15 @@ class ModeParser {
             } else {
                 PersistenceManager.LOG.log(Level.INFO,
                 "[WinSys.ModeParser.handleEmptyBehavior]" // NOI18N
-                + " Warning: Invalid value of attribute \"permanent\"."); // NOI18N
+                + " Warning: Invalid value " + value + " of attribute \"permanent\"."); // NOI18N
                 modeConfig.permanent = false;
             }
         }
         
+        StringBuffer generateData(ModeConfig mc, InternalConfig ic) throws IOException {
+            return fillBuffer(mc, ic);
+        }
+        
         /** Writes data from asociated mode to the xml representation */
         void writeData (ModeConfig mc, InternalConfig ic) throws IOException {
             final StringBuffer buff = fillBuffer(mc, ic);
diff --git a/platform/core.windows/src/org/netbeans/core/windows/persistence/PersistenceManager.java b/platform/core.windows/src/org/netbeans/core/windows/persistence/PersistenceManager.java
index b15fb38..00fe28d 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/persistence/PersistenceManager.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/persistence/PersistenceManager.java
@@ -59,7 +59,6 @@ import org.openide.util.Exceptions;
 import org.openide.util.Lookup;
 import org.openide.util.NbBundle;
 import org.openide.util.Utilities;
-import org.openide.util.io.SafeException;
 import org.openide.windows.TopComponent;
 import org.openide.xml.XMLUtil;
 import org.xml.sax.EntityResolver;
@@ -99,6 +98,8 @@ public final class PersistenceManager implements PropertyChangeListener {
     /** default base name for noname top components */
     private static final String DEFAULT_TC_NAME = "untitled_tc"; // NOI18N
     
+    private static final String UNNAMED_MODE_PARSER = "unnamed_mp"; // NOI18N
+            
     private static final boolean DEBUG = Debug.isLoggable(PersistenceManager.class);
     
     /** Root folder for win sys module */
@@ -1364,6 +1365,16 @@ public final class PersistenceManager implements PropertyChangeListener {
         }
         return tcName;
     }
+
+    public ModeConfig createModeFromXml(String xml) throws IOException {
+        ModeParser modeParser = ModeParser.parseFromString(UNNAMED_MODE_PARSER, new HashSet());
+        return modeParser.load(xml);
+    }
+    
+    public String createXmlFromMode(ModeConfig modeConfig) throws IOException {
+        ModeParser modeParser = ModeParser.parseFromString(UNNAMED_MODE_PARSER, new HashSet());
+        return modeParser.modeConfigXml(modeConfig);
+    }
     
     /**
      * This class is used to clean internal maps containing <String,WeakReference<TopComponent>>
diff --git a/platform/core.windows/src/org/netbeans/core/windows/persistence/WindowManagerParser.java b/platform/core.windows/src/org/netbeans/core/windows/persistence/WindowManagerParser.java
index 685e261..7ea9aff 100644
--- a/platform/core.windows/src/org/netbeans/core/windows/persistence/WindowManagerParser.java
+++ b/platform/core.windows/src/org/netbeans/core/windows/persistence/WindowManagerParser.java
@@ -98,6 +98,37 @@ public class WindowManagerParser {
         }
     }
     
+    /** Extract all ModeConfigs in XML.
+     *
+     * @param wmc where all knowledge of the ModeConfigs is kept
+     * @return All the known modes in the XML configuration that they would
+     * be saved to disk as.
+     * @throws IOException
+     */
+    List<String> modeConfigXmls(WindowManagerConfig wmc) throws IOException {
+        synchronized (SAVING_LOCK) {
+            Map<String, ModeConfig> modeConfigMap = gatherModeConfigs(wmc);
+            return xmlModeConfigs(modeConfigMap);
+        }
+    }
+    
+    /** Get a ModeConfig as XML.
+     * 
+     * @param modeName the name of the Mode for which we want the XML
+     * @param wmc where all knowledge of the ModeConfigs is kept
+     * @return XML String of the Mode with modeName
+     * @throws IOException 
+     */
+    public String modeConfigXml(String modeName, WindowManagerConfig wmc) throws IOException {
+        synchronized (SAVING_LOCK) {
+            Map<String, ModeConfig> modeConfigMap = gatherModeConfigs(wmc);
+            ModeConfig modeConfig = modeConfigMap.get(modeName);
+            ModeParser modeParser = modeParserMap.get(modeName);
+            String xml = modeParser.modeConfigXml(modeConfig);
+            return xml;
+        }
+    }
+
     /** Called from ModuleChangeHandler when wsmode file is deleted from module folder.
      * Do not remove ModeParser. Only set that it is not present in module folder.
      * @param modeName unique name of mode
@@ -123,7 +154,7 @@ public class WindowManagerParser {
             ModeParser modeParser = (ModeParser) modeParserMap.get(modeName);
             if (modeParser == null) {
                 //Create new ModeParser if it does not exist.
-                modeParser = new ModeParser(modeName,tcRefNameLocalSet);
+                modeParser = ModeParser.parseFromFileObject(modeName,tcRefNameLocalSet);
                 modeParserMap.put(modeName, modeParser);
             }
             FileObject modesModuleFolder = null;
@@ -360,7 +391,7 @@ public class WindowManagerParser {
                     //wsmode file
                     ModeParser modeParser = (ModeParser) modeParserMap.get(files[i].getName());
                     if (modeParser == null) {
-                        modeParser = new ModeParser(files[i].getName(),tcRefNameLocalSet);
+                        modeParser = ModeParser.parseFromFileObject(files[i].getName(),tcRefNameLocalSet);
                         modeParserMap.put(files[i].getName(), modeParser);
                     }
                     modeParser.setInModuleFolder(true);
@@ -382,7 +413,7 @@ public class WindowManagerParser {
                     if (modeParserMap.containsKey(files[i].getName())) {
                         modeParser = (ModeParser) modeParserMap.get(files[i].getName());
                     } else {
-                        modeParser = new ModeParser(files[i].getName(),tcRefNameLocalSet);
+                        modeParser = ModeParser.parseFromFileObject(files[i].getName(),tcRefNameLocalSet);
                         modeParserMap.put(files[i].getName(), modeParser);
                     }
                     modeParser.setInLocalFolder(true);
@@ -644,8 +675,53 @@ public class WindowManagerParser {
         if (DEBUG) Debug.log(WindowManagerParser.class, "writeProperties LEAVE");
     }
     
-    private void writeModes (WindowManagerConfig wmc) throws IOException {
-        if (DEBUG) Debug.log(WindowManagerParser.class, "writeModes ENTER");
+    private void writeModes(WindowManagerConfig wmc) throws IOException {
+        if (DEBUG) {
+            Debug.log(WindowManagerParser.class, "writeModes ENTER");
+        }
+
+        Map<String, ModeConfig> modeConfigMap = gatherModeConfigs(wmc);
+
+        saveModeConfigs(modeConfigMap);
+
+        if (DEBUG) {
+            Debug.log(WindowManagerParser.class, "writeModes LEAVE");
+        }
+    }
+
+    private void saveModeConfigs(Map<String, ModeConfig> modeConfigMap) throws IOException {
+        FileObject modesLocalFolder = pm.getRootLocalFolder().getFileObject(PersistenceManager.MODES_FOLDER);
+        if ((modesLocalFolder == null) && (modeParserMap.size() > 0)) {
+            modesLocalFolder = pm.getModesLocalFolder();
+        }
+        //Step 3: Save all modes
+        for (Iterator it = modeParserMap.keySet().iterator(); it.hasNext();) {
+            ModeParser modeParser = (ModeParser) modeParserMap.get(it.next());
+            modeParser.setLocalParentFolder(modesLocalFolder);
+            modeParser.setInLocalFolder(true);
+            modeParser.save((ModeConfig) modeConfigMap.get(modeParser.getName()));
+        }
+    }
+
+    private List<String> xmlModeConfigs(Map<String, ModeConfig> modeConfigMap) throws IOException {
+        // Convert ModeConfigs into xml.
+        List<String> modeConfigXmls = new ArrayList<>();
+        for (Iterator it = modeParserMap.keySet().iterator(); it.hasNext();) {
+            ModeParser modeParser = (ModeParser) modeParserMap.get(it.next());
+            String xml = modeParser.modeConfigXml((ModeConfig) modeConfigMap.get(modeParser.getName()));
+            modeConfigXmls.add(xml);
+        }
+        return modeConfigXmls;
+    }
+
+    /**
+     * Cleans obsolete ModeParsers and creates missing ModeParsers, populating
+     * modeParserMap
+     *
+     * @param wmc where all knowledge of the ModeConfigs is kept
+     * @return named index of Mode configurations
+     */
+    private Map<String, ModeConfig> gatherModeConfigs(WindowManagerConfig wmc) {
         //Step 1: Clean obsolete mode parsers
         Map<String, ModeConfig> modeConfigMap = new HashMap<String, ModeConfig>();
         for (int i = 0; i < wmc.modes.length; i++) {
@@ -668,28 +744,15 @@ public class WindowManagerParser {
         //Step 2: Create missing mode parsers
         for (int i = 0; i < wmc.modes.length; i++) {
             if (!modeParserMap.containsKey(wmc.modes[i].name)) {
-                ModeParser modeParser = new ModeParser(wmc.modes[i].name,tcRefNameLocalSet);
+                ModeParser modeParser = ModeParser.parseFromFileObject(wmc.modes[i].name,tcRefNameLocalSet);
                 modeParserMap.put(wmc.modes[i].name, modeParser);
                 //if (DEBUG) Debug.log(WindowManagerParser.class, "-- WMParser.writeModes ** CREATE modeParser:" + modeParser.getName());
             }
         }
-        
-        FileObject modesLocalFolder = pm.getRootLocalFolder().getFileObject(PersistenceManager.MODES_FOLDER);
-        if ((modesLocalFolder == null) && (modeParserMap.size() > 0)) {
-            modesLocalFolder = pm.getModesLocalFolder();
-        }
-        //Step 3: Save all modes
-        for (Iterator it = modeParserMap.keySet().iterator(); it.hasNext(); ) {
-            ModeParser modeParser = (ModeParser) modeParserMap.get(it.next());
-            modeParser.setLocalParentFolder(modesLocalFolder);
-            modeParser.setInLocalFolder(true);
-            modeParser.save((ModeConfig) modeConfigMap.get(modeParser.getName()));
-        }
-        
-        if (DEBUG) Debug.log(WindowManagerParser.class, "writeModes LEAVE");
+        return modeConfigMap;
     }
-    
-    private void writeGroups (WindowManagerConfig wmc) throws IOException {
+
+    private void writeGroups(WindowManagerConfig wmc) throws IOException {
         if (DEBUG) Debug.log(WindowManagerParser.class, "writeGroups ENTER");
         //Step 1: Clean obsolete group parsers
         Map<String, GroupConfig> groupConfigMap = new HashMap<String, GroupConfig>();
@@ -1948,7 +2011,7 @@ public class WindowManagerParser {
      */
     public static ModeConfig loadModeConfigFrom( FileObject fo ) throws IOException {
         String modeName = fo.getName();
-        ModeParser parser = new ModeParser(modeName, new HashSet(1));
+        ModeParser parser = ModeParser.parseFromFileObject(modeName, new HashSet(1));
         parser.setInLocalFolder(true);
         parser.setLocalParentFolder(fo.getParent());
         return parser.load();
diff --git a/platform/core.windows/test/unit/src/org/netbeans/core/windows/WindowManagerModeTest.java b/platform/core.windows/test/unit/src/org/netbeans/core/windows/WindowManagerModeTest.java
new file mode 100644
index 0000000..680ce1b
--- /dev/null
+++ b/platform/core.windows/test/unit/src/org/netbeans/core/windows/WindowManagerModeTest.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.core.windows;
+
+import java.awt.GraphicsEnvironment;
+import static junit.framework.TestCase.assertNotNull;
+import junit.framework.TestSuite;
+import org.netbeans.junit.NbTestCase;
+import org.openide.modules.ModuleInfo;
+import org.openide.util.Lookup;
+import org.openide.windows.Mode;
+import org.openide.windows.ModeUtilities;
+import org.openide.windows.TopComponent;
+import org.openide.windows.WindowManager;
+
+/**
+ * This test exercises the Mode management methods of WindowManager 
+ * and Mode XML extraction via ModeUtilities.
+ * <br>
+ * <br>
+ * It would be better placed in Windows System API but it needs
+ * PersistenceHandler to load the window system.
+ *
+ * @author Mark Phipps
+ */
+public class WindowManagerModeTest extends NbTestCase {
+
+    public static junit.framework.Test suite() {
+        return GraphicsEnvironment.isHeadless() ? new TestSuite() : new TestSuite(WindowManagerModeTest.class);
+    }
+
+    private static boolean loaded = false;
+
+    private final String anonymousModeXml
+            = "<mode version=\"2.4\">"
+            + "<name unique=\"anonymousMode_1\"/>"
+            + "<kind type=\"editor\"/>"
+            + "<state type=\"joined\"/>"
+            + "<constraints>"
+            + "<path weight=\"0.5\" number=\"1\" orientation=\"horizontal\"/>"
+            + "</constraints>"
+            + "<bounds height=\"0\" width=\"0\" y=\"0\" x=\"0\"/>"
+            + "<frame state=\"0\"/>"
+            + "<empty-behavior permanent=\"false\"/>"
+            + "</mode>";
+
+    private final String editorModeXml
+            = "<mode version=\"2.4\">"
+            + "<name unique=\"editor\"/>"
+            + "<kind type=\"editor\"/>"
+            + "<state type=\"joined\"/>"
+            + "<constraints>"
+            + "<path weight=\"0.5\" number=\"0\" orientation=\"horizontal\" />"
+            + "</constraints>"
+            + "<bounds height=\"0\" width=\"0\" y=\"0\" x=\"0\" />"
+            + "<frame state=\"0\" />"
+            + "<empty-behavior permanent=\"true\" />"
+            + "</mode>";
+
+    public WindowManagerModeTest(String testName) {
+        super(testName);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        if (!loaded) {
+            // Load just once for all tests in this class
+            Lookup.getDefault().lookup(ModuleInfo.class);
+            PersistenceHandler.getDefault().load();
+            loaded = true;
+        }
+    }
+
+    @Override
+    protected boolean runInEQ() {
+        return true;
+    }
+
+    public void testGetEditorModeXml() {
+
+        WindowManager wm = WindowManager.getDefault();
+        assertNotNull(wm);
+
+        Mode mode = wm.findMode("editor");
+
+        String xml = ModeUtilities.toXml(mode);
+        assertNotNull("editor Mode XML should not be null", xml);
+
+    }
+
+    public void testCreateAndRemoveAnonymousMode() {
+
+        WindowManager wm = WindowManager.getDefault();
+        assertNotNull(wm);
+
+        Mode mode = wm.createModeFromXml(anonymousModeXml);
+        assertNotNull("Anonymous Mode should have been created", mode);
+        
+        assertEquals("Anonymous Mode should be find-able", mode, wm.findMode(mode.getName()));
+
+        assertTrue("Anonymous Mode should have been removed", wm.removeMode(mode));
+    }
+    
+    public void testUpdateModeConstraints() {
+        
+        WindowManager wm = WindowManager.getDefault();
+        assertNotNull(wm);
+        
+        boolean updated = wm.updateModeConstraintsFromXml(editorModeXml);
+        
+        assertTrue("Should have found and updated the editor Mode", updated);
+    }
+
+    public void testExampleWorkFlowSaveAndLoadWorkSpace() {
+        WindowManager wm = WindowManager.getDefault();
+        assertNotNull(wm);
+
+        // User creates TopComponents in the application, drags them around.
+        TopComponent testTc = new TopComponent();
+        wm.findMode("editor").dockInto(testTc);
+        testTc.open();
+
+        // Let's pretend thet the user dragged testTc into a new anonymous Mode.
+        // Now wm.findMode(testTc).getName() would be "anonymousMode_1";
+        
+        // User decides to save the layout.
+        for (Mode mode : wm.getModes()) {
+            String xml = ModeUtilities.toXml(mode);
+            // Save the Mode xml somehow...
+        }
+        
+        for (TopComponent tc : wm.getRegistry().getOpened()) {
+            // Save the state of the TopComponent somehow...
+            // Also save the name of the Mode the TopComponent was docked into:
+            String modeName = wm.findMode(tc).getName();
+        }
+                
+        // Later you restore the layout...
+        // Close open TopComponents.
+        for (TopComponent tc : wm.getRegistry().getOpened()) {
+            tc.close();
+        }
+        
+        // Remove unwanted Modes.
+        for (Mode mode: wm.getModes()) {
+            if (mode.getName().startsWith("anonymous")) {
+                wm.removeMode(mode);
+            }
+        }
+        
+        // Restore the XML of the Modes somehow...
+        String[] modeXmls = new String[] {editorModeXml, anonymousModeXml};
+        for (String modeXml: modeXmls) {
+            // Use some XML magic of your choice to determine if this is
+            // an anonymous Mode or a defined Mode.
+            if (modeXml.contains("anonymous")) {
+                // Create the new Mode
+                wm.createModeFromXml(modeXml);
+            } else {
+                // Adjust the constraints of defined Modes.
+                wm.updateModeConstraintsFromXml(modeXml);
+            }
+        }
+        
+        // Restore the TopComponents and the names of their Modes somehow...
+        testTc = new TopComponent();
+        // Earlier in the test we pretended that testTc was dragged into anonymousMode_1.
+        wm.findMode("anonymousMode_1").dockInto(testTc);
+        assertEquals("anonymousMode_1", wm.findMode(testTc).getName());
+        
+    }
+}
diff --git a/platform/core.windows/test/unit/src/org/netbeans/core/windows/persistence/ModeParserTest.java b/platform/core.windows/test/unit/src/org/netbeans/core/windows/persistence/ModeParserTest.java
index 8cb2203..2d43f83 100644
--- a/platform/core.windows/test/unit/src/org/netbeans/core/windows/persistence/ModeParserTest.java
+++ b/platform/core.windows/test/unit/src/org/netbeans/core/windows/persistence/ModeParserTest.java
@@ -613,7 +613,7 @@ public class ModeParserTest extends NbTestCase {
         assertNotNull("Test parent folder not found. ParentFolder is null.",url);
 
         Set setLocal = new HashSet();
-        ModeParser modeParser = new ModeParser(name,setLocal);
+        ModeParser modeParser = ModeParser.parseFromFileObject(name,setLocal);
         modeParser.setInLocalFolder(true);
         modeParser.setLocalParentFolder(parentFolder);
         
diff --git a/platform/openide.windows/apichanges.xml b/platform/openide.windows/apichanges.xml
index e8a167e..268f916 100644
--- a/platform/openide.windows/apichanges.xml
+++ b/platform/openide.windows/apichanges.xml
@@ -26,6 +26,23 @@
 <apidef name="winsys">Window System API</apidef>
 </apidefs>
 <changes>
+    <change id="ModeCreation">
+        <api name="winsys"/>
+        <summary>Allow Modes to be created directly from ModeConfig XML</summary>
+        <version major="6" minor="6.81"/>
+        <date day="18" month="3" year="2019"/>
+        <author login="phipma"/>
+        <compatibility addition="yes" modification="yes"/>
+        <description>
+            <p>
+                Plugin implementors can save and restore individual Modes providing
+                scope for custom TopComponent layouts.
+            </p>
+        </description>
+        <class package="org.openide.windows" name="Mode"/>
+        <class package="org.openide.windows" name="ModeUtilities"/>
+        <class package="org.openide.windows" name="WindowManager"/>
+    </change>
     <change id="ModeSelector">
         <api name="winsys"/>
         <summary>Allow to select Mode for opening a TopComponent instance</summary>
diff --git a/platform/openide.windows/manifest.mf b/platform/openide.windows/manifest.mf
index 71df22f..db311fe 100644
--- a/platform/openide.windows/manifest.mf
+++ b/platform/openide.windows/manifest.mf
@@ -1,6 +1,6 @@
 Manifest-Version: 1.0
 OpenIDE-Module: org.openide.windows
-OpenIDE-Module-Specification-Version: 6.81
+OpenIDE-Module-Specification-Version: 6.82
 OpenIDE-Module-Localizing-Bundle: org/openide/windows/Bundle.properties
 AutoUpdate-Essential-Module: true
 
diff --git a/platform/openide.windows/src/org/openide/windows/Mode.java b/platform/openide.windows/src/org/openide/windows/Mode.java
index 36d2f19..57adfd4 100644
--- a/platform/openide.windows/src/org/openide/windows/Mode.java
+++ b/platform/openide.windows/src/org/openide/windows/Mode.java
@@ -124,4 +124,20 @@ public interface Mode extends Serializable {
     /** Gets selected <code>TopComponent</code> in this mode.
      * @since 4.13 */
     public TopComponent getSelectedTopComponent();
+    
+    /**
+     * Extension to provide exposure of XML configuration.
+     * 
+     * @see http://wiki.apidesign.org/wiki/ExtendingInterfaces
+     * @since 6.82
+     */
+    public interface Xml extends Mode {
+        
+        /**
+         * Generates the Mode configuration as XML.
+         * @return an XML representation of the Mode's configuration.
+         * @since 6.82
+         */
+        public String toXml();
+    }
 }
diff --git a/platform/openide.windows/src/org/openide/windows/ModeUtilities.java b/platform/openide.windows/src/org/openide/windows/ModeUtilities.java
new file mode 100644
index 0000000..796e5fc
--- /dev/null
+++ b/platform/openide.windows/src/org/openide/windows/ModeUtilities.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.windows;
+
+/**
+ * Utility class to help clients manage {@link Mode}s in order to layout TopComponents
+ * in predefined "work-spaces".
+ * <br>
+ * <br>
+ * In a NetBeans Platform application, a user may create various TopComponents and
+ * drag them around, into and out of the existing defined Modes (e.g. editor, explorer) or 
+ * new "anonymous" Modes which get created by the system as needed.
+ * <br>
+ * <br>
+ * The configuration of each Mode can be expressed in XML. This class gives access
+ * to that XML. {@link WindowManager} provides a 
+ * {@link WindowManager#createModeFromXml mechanism to reproduce a Mode from XML}
+ * so you can construct ways to save Mode and TopComponent combinations, which can 
+ * subsequently be chosen and reloaded at a later time.
+ * <br>
+ * <br>
+ * Note that this is a different approach to how the Platform tends to save the layout
+ * of TopComponents at shut-down time in the Windows2Local file system. Note also that 
+ * this is not connected with the deprecated notion of {@link Workspace}.
+ * 
+ * @see http://wiki.apidesign.org/wiki/ExtendingInterfaces
+ * 
+ * @author Mark Phipps
+ * @since 6.82
+ */
+public final class ModeUtilities {
+
+    private ModeUtilities() {
+    }
+
+    /**
+     * Expose the Mode's configuration as XML.
+     * 
+     * @param mode the {@link Mode} whose XML configuration is required.
+     * @return the XML of the Mode's configuration or {@code null} if not supported.
+     */
+    public final static String toXml(Mode mode) {
+        return mode instanceof Mode.Xml
+                ? ((Mode.Xml) mode).toXml()
+                : null;
+    }
+}
diff --git a/platform/openide.windows/src/org/openide/windows/WindowManager.java b/platform/openide.windows/src/org/openide/windows/WindowManager.java
index 1d7d97e..7717b6c 100644
--- a/platform/openide.windows/src/org/openide/windows/WindowManager.java
+++ b/platform/openide.windows/src/org/openide/windows/WindowManager.java
@@ -741,6 +741,44 @@ public abstract class WindowManager extends Object implements Serializable {
         return null;
     }
     
+    /**
+     * Given some XML, attempts to create a Mode that can
+     * subsequently be used to dock a TopComponent into.
+     * Usually this will be an anonymous Mode.
+     * 
+     * @param xml ModeConfig XML that was originally produced by {@link ModeUtilities#toXml}
+     * @return an instance of Mode or null if the attempt to create the Mode failed
+     * @see ModeUtilities
+     */
+    public Mode createModeFromXml(String xml) {
+        return null;
+    }
+
+    /**
+     * Before restoring a whole bunch of Modes (for example with XML that has been
+     * previously saved somewhere and now loaded), it is useful to remove the
+     * anonymous modes from the system.
+     * 
+     * @param mode the {@link Mode} to be removed
+     * @return success or failure of the attempt to remove the {@link Mode}
+     */
+    public boolean removeMode(Mode mode) {
+        return false;
+    }
+
+    /**
+     * Before restoring anonymous Modes, it is useful to update whatever defined Modes
+     * may exist like editor, explorer etc., so that all the Modes will eventually
+     * re-appear in the desired locations.
+     * 
+     * @param xml ModeConfig XML that was originally produced by {@link ModeUtilities#toXml}
+     * @return success or failure of the attempt to find the Mode and update it
+     * @see ModeUtilities
+     */
+    public boolean updateModeConstraintsFromXml(String xml) {
+        return false;
+    }
+            
     /** A manager that handles operations on top components.
      * It is always attached to a {@link TopComponent}.
      * @deprecated Do not use anymore. This interface is replaced by bunch of protected methods


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists