001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    
029    package edu.wisc.ssec.mcidasv;
030    
031    import java.awt.Insets;
032    import java.awt.Rectangle;
033    import java.awt.event.ActionEvent;
034    import java.awt.event.ActionListener;
035    import java.io.File;
036    import java.io.FileOutputStream;
037    import java.io.IOException;
038    import java.util.ArrayList;
039    import java.util.Collection;
040    import java.util.Collections;
041    import java.util.Enumeration;
042    import java.util.HashSet;
043    import java.util.Hashtable;
044    import java.util.LinkedHashMap;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.Set;
048    import java.util.zip.ZipEntry;
049    import java.util.zip.ZipInputStream;
050    
051    import javax.swing.ButtonGroup;
052    import javax.swing.JCheckBox;
053    import javax.swing.JComboBox;
054    import javax.swing.JComponent;
055    import javax.swing.JLabel;
056    import javax.swing.JPanel;
057    import javax.swing.JRadioButton;
058    import javax.swing.JTextField;
059    
060    import org.apache.batik.util.DoublyIndexedTable.Entry;
061    import org.python.core.PyObject;
062    import org.slf4j.Logger;
063    import org.slf4j.LoggerFactory;
064    import org.w3c.dom.Document;
065    import org.w3c.dom.Element;
066    import org.w3c.dom.Node;
067    
068    import ucar.unidata.data.DataChoice;
069    import ucar.unidata.data.DataSource;
070    import ucar.unidata.data.DataSourceDescriptor;
071    import ucar.unidata.data.DataSourceImpl;
072    import ucar.unidata.idv.DisplayControl;
073    import ucar.unidata.idv.IdvManager;
074    import ucar.unidata.idv.IdvObjectStore;
075    import ucar.unidata.idv.IdvPersistenceManager;
076    import ucar.unidata.idv.IdvResourceManager;
077    import ucar.unidata.idv.IntegratedDataViewer;
078    import ucar.unidata.idv.MapViewManager;
079    import ucar.unidata.idv.SavedBundle;
080    import ucar.unidata.idv.ServerUrlRemapper;
081    import ucar.unidata.idv.ViewDescriptor;
082    import ucar.unidata.idv.ViewManager;
083    import ucar.unidata.idv.control.DisplayControlImpl;
084    import ucar.unidata.idv.ui.IdvComponentGroup;
085    import ucar.unidata.idv.ui.IdvComponentHolder;
086    import ucar.unidata.idv.ui.IdvUIManager;
087    import ucar.unidata.idv.ui.IdvWindow;
088    import ucar.unidata.idv.ui.IdvXmlUi;
089    import ucar.unidata.idv.ui.LoadBundleDialog;
090    import ucar.unidata.idv.ui.WindowInfo;
091    import ucar.unidata.ui.ComponentGroup;
092    import ucar.unidata.util.ColorTable;
093    import ucar.unidata.util.FileManager;
094    import ucar.unidata.util.GuiUtils;
095    import ucar.unidata.util.IOUtil;
096    import ucar.unidata.util.LogUtil;
097    import ucar.unidata.util.Misc;
098    import ucar.unidata.util.PollingInfo;
099    import ucar.unidata.util.StringUtil;
100    import ucar.unidata.util.Trace;
101    import ucar.unidata.util.TwoFacedObject;
102    import ucar.unidata.xml.XmlResourceCollection;
103    import edu.wisc.ssec.mcidasv.control.ImagePlanViewControl;
104    import edu.wisc.ssec.mcidasv.probes.ReadoutProbe;
105    import edu.wisc.ssec.mcidasv.ui.McIDASVXmlUi;
106    import edu.wisc.ssec.mcidasv.ui.McvComponentGroup;
107    import edu.wisc.ssec.mcidasv.ui.McvComponentHolder;
108    import edu.wisc.ssec.mcidasv.ui.UIManager;
109    import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
110    import edu.wisc.ssec.mcidasv.util.XPathUtils;
111    import edu.wisc.ssec.mcidasv.util.XmlUtil;
112    
113    /**
114     * <p>McIDAS-V has 99 problems, and bundles are several of 'em. Since the UI of
115     * alpha 10 relies upon component groups and McIDAS-V needs to support IDV and
116     * bundles prior to alpha 10, we must add facilities for coping with bundles
117     * that may not contain component groups. Here's a list of the issues and how
118     * they are resolved:</p> 
119     * 
120     * <p><ul>
121     * <li>Bundles prior to alpha 9 use the <code>TabbedUIManager</code>. Each tab
122     * is, internally, an IDV window. This is reflected in the contents of bundles,
123     * so the IDV wants to create a new window for each tab upon loading. Alpha 10
124     * allows the user to force bundles to only create one window. This work is
125     * done in {@link #injectComponentGroups(List)}.</li>
126     * 
127     * <li>The IDV allows users to save bundles that contain <i>both</i> 
128     * {@link ucar.unidata.idv.ViewManager}s with component groups and without! 
129     * This is actually only a problem when limiting the windows; 
130     * <code>injectComponentGroups</code> has to wrap ViewManagers without 
131     * component groups in dynamic skins. These ViewManagers must be removed 
132     * from the bundle's internal list of ViewManagers, as they don't exist until
133     * the dynamic skin is built. <i>Do not simply clear the list!</i> The 
134     * ViewManagers within component groups must appear in it, otherwise the IDV
135     * does not add them to the {@link ucar.unidata.idv.VMManager}. If limiting 
136     * windows is off, everything will be caught properly by the unpersisting 
137     * facilities in {@link edu.wisc.ssec.mcidasv.ui.UIManager}.</li>
138     * 
139     * @see IdvPersistenceManager
140     * @see UIManager
141     */
142    public class PersistenceManager extends IdvPersistenceManager {
143    
144        /** Key used to access a bundle's McIDAS-V in-depth versioning info section. */
145        public static final String ID_MCV_VERSION = "mcvversion";
146    
147        private static final Logger logger = LoggerFactory.getLogger(PersistenceManager.class);
148    
149        /**
150         * Macro used as a place holder for wherever the IDV decides to place 
151         * extracted contents of a bundle. 
152         */
153        public static final String MACRO_ZIDVPATH = '%'+PROP_ZIDVPATH+'%';
154    
155        static ucar.unidata.util.LogUtil.LogCategory log_ =
156            ucar.unidata.util.LogUtil.getLogInstance(IdvManager.class.getName());
157    
158        /** Is the bundle being saved a layout bundle? */
159        private boolean savingDefaultLayout = false;
160    
161        /** Stores the last active ViewManager from <i>before</i> a bundle load. */
162        private ViewManager lastBeforeBundle = null;
163    
164        /** 
165         * Whether or not the user wants to attempt merging bundled layers into
166         * current displays.
167         */
168        private boolean mergeBundledLayers = false;
169    
170        /** Whether or not a bundle is actively loading. */
171        private boolean bundleLoading = false;
172    
173        /** Cache the parameter sets XML */
174        private XmlResourceCollection parameterSets;
175        private static Document parameterSetsDocument;
176        private static Element parameterSetsRoot;
177        private static final String TAG_FOLDER = "folder";
178        private static final String TAG_DEFAULT = "default";
179        private static final String ATTR_NAME = "name";
180    
181        /** Use radio buttons to control state saving */
182        private JRadioButton layoutOnlyRadio;
183        private JRadioButton layoutSourcesRadio;
184        private JRadioButton layoutSourcesDataRadio;
185        
186        /**
187         * Java requires this constructor. 
188         */
189        public PersistenceManager() {
190            this(null);
191        }
192    
193        /**
194         * @see ucar.unidata.idv.IdvPersistenceManager#IdvPersistenceManager(IntegratedDataViewer)
195         */
196        public PersistenceManager(IntegratedDataViewer idv) {
197            super(idv);
198               
199            //TODO: Saved for future development
200    /**
201            layoutOnlyRadio = new JRadioButton("Layout only");
202            layoutOnlyRadio.addActionListener(new ActionListener() {
203                public void actionPerformed(final ActionEvent e) {
204                    saveJythonBox.setSelectedIndex(0);
205                    saveJython = false;
206                    makeDataRelativeCbx.setSelected(false);
207                    makeDataRelative = false;
208                    saveDataSourcesCbx.setSelected(false);
209                    saveDataSources = false;
210                    saveDataCbx.setSelected(false);
211                    saveData = false;
212                }
213            });
214    
215            layoutSourcesRadio = new JRadioButton("Layout & Data Sources");
216            layoutSourcesRadio.addActionListener(new ActionListener() {
217                public void actionPerformed(final ActionEvent e) {
218                    saveJythonBox.setSelectedIndex(1);
219                    saveJython = true;
220                    makeDataRelativeCbx.setSelected(false);
221                    makeDataRelative = false;
222                    saveDataSourcesCbx.setSelected(true);
223                    saveDataSources = true;
224                    saveDataCbx.setSelected(false);
225                    saveData = false;
226                }
227            });
228            
229            layoutSourcesDataRadio = new JRadioButton("Layout, Data Sources & Data");
230            layoutSourcesRadio.addActionListener(new ActionListener() {
231                public void actionPerformed(final ActionEvent e) {
232                    saveJythonBox.setSelectedIndex(1);
233                    saveJython = true;
234                    makeDataRelativeCbx.setSelected(false);
235                    makeDataRelative = false;
236                    saveDataSourcesCbx.setSelected(true);
237                    saveDataSources = true;
238                    saveDataCbx.setSelected(true);
239                    saveData = true;
240                }
241            });
242            //Group the radio buttons.
243            layoutSourcesRadio.setSelected(true);
244            ButtonGroup group = new ButtonGroup();
245            group.add(layoutOnlyRadio);
246            group.add(layoutSourcesRadio);
247            group.add(layoutSourcesDataRadio);
248    */
249            
250        }
251    
252        /**
253         * Returns the last active {@link ViewManager} from <i>before</i> loading
254         * the most recent bundle.
255         * 
256         * @return Either the ViewManager or {@code null} if there was no previous
257         * ViewManager (such as loading a default bundle/layout).
258         */
259        public ViewManager getLastViewManager() {
260            return lastBeforeBundle;
261        }
262    
263        /**
264         * Returns whether or not a bundle is currently being loaded.
265         * 
266         * @return Either {@code true} if {@code instantiateFromBundle} is doing 
267         * what it needs to do, or {@code false}.
268         * 
269         * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean, boolean)
270         */
271        public boolean isBundleLoading() {
272            return bundleLoading;
273        }
274    
275        public boolean getMergeBundledLayers() {
276            logger.trace("mergeBundledLayers={}", mergeBundledLayers);
277            return mergeBundledLayers;
278        }
279    
280        private void setMergeBundledLayers(final boolean newValue) {
281            logger.trace("old={} new={}", mergeBundledLayers, newValue);
282            mergeBundledLayers = newValue;
283        }
284    
285        @Override public boolean getSaveDataSources() {
286            boolean result = false;
287            if (!savingDefaultLayout) {
288                result = super.getSaveDataSources();
289            }
290            logger.trace("getSaveDataSources={} savingDefaultLayout={}", result, savingDefaultLayout);
291            return result;
292        }
293    
294        @Override public boolean getSaveDisplays() {
295            boolean result = false;
296            if (!savingDefaultLayout) {
297                result = super.getSaveDisplays();
298            }
299            logger.trace("getSaveDisplays={} savingDefaultLayout={}", result, savingDefaultLayout);
300            return result;
301        }
302    
303        @Override public boolean getSaveViewState() {
304            boolean result = true;
305            if (!savingDefaultLayout) {
306                result = super.getSaveViewState();
307            }
308            logger.trace("getSaveViewState={} savingDefaultLayout={}", result, savingDefaultLayout);
309            return result;
310        }
311    
312        @Override public boolean getSaveJython() {
313            boolean result = false;
314            if (!savingDefaultLayout) {
315                result = super.getSaveJython();
316            }
317            logger.trace("getSaveJython={} savingDefaultLayout={}", result, savingDefaultLayout);
318            return result;
319        }
320    
321        public void doSaveAsDefaultLayout() {
322            String layoutFile = getResourceManager().getResources(IdvResourceManager.RSC_BUNDLES).getWritable();
323            // do prop check here?
324            File f = new File(layoutFile);
325            if (f.exists()) {
326                boolean result = GuiUtils.showYesNoDialog(null, "Saving a new default layout will overwrite your existing default layout. Do you wish to continue?", "Overwrite Confirmation");
327                if (!result) {
328                    return;
329                }
330            }
331    
332            savingDefaultLayout = true;
333            try {
334                String xml = getBundleXml(true, true);
335                if (xml != null) {
336                    IOUtil.writeFile(layoutFile, xml);
337                }
338            } catch (Exception e) {
339                e.printStackTrace();
340            } finally {
341                savingDefaultLayout = false;
342            }
343        }
344    
345        @Override public JPanel getFileAccessory() {
346            // Always save displays and data sources
347            saveDisplaysCbx.setSelected(true);
348            saveDisplays = true;
349            saveViewStateCbx.setSelected(true);
350            saveViewState = true;
351            saveDataSourcesCbx.setSelected(true);
352            saveDataSources = true;
353    
354            return GuiUtils.top(
355                GuiUtils.vbox(
356                    Misc.newList(
357                        GuiUtils.inset(new JLabel("Bundle save options:"),
358                                       new Insets(0, 5, 5, 0)),
359                                       saveJythonBox,
360                                       makeDataRelativeCbx)));
361        }
362        
363        /**
364         * Have the user select an xidv filename and
365         * write the current application state to it.
366         * This also sets the current file name and
367         * adds the file to the history list.
368         */
369        public void doSaveAs() {
370            String filename =
371                FileManager.getWriteFile(getArgsManager().getBundleFileFilters(),
372                                         "mcvz", getFileAccessory());
373            if (filename == null) {
374                return;
375            }
376            setCurrentFileName(filename);
377    
378            boolean prevMakeDataEditable = makeDataEditable;
379            makeDataEditable = makeDataEditableCbx.isSelected();
380    
381            boolean prevMakeDataRelative = makeDataRelative;
382            makeDataRelative = makeDataRelativeCbx.isSelected();
383            if (doSave(filename)) {
384                getPublishManager().publishContent(filename, null, publishCbx);
385                getIdv().addToHistoryList(filename);
386            }
387            makeDataEditable = prevMakeDataEditable;
388            makeDataRelative = prevMakeDataRelative;
389    
390        }
391    
392        /**
393         * Overridden so that McIDAS-V can: 
394         * <ul>
395         * <li>add better versioning information to bundles</li>
396         * <li>remove {@link edu.wisc.ssec.mcidasv.probes.ReadoutProbe ReadoutProbes} from the {@code displayControls} that are getting persisted.</li>
397         * <li>disallow saving multi-banded ADDE data sources until we have fix!</li>
398         * </ul>
399         */
400        @Override protected boolean addToBundle(Hashtable data, List dataSources,
401                List displayControls, List viewManagers,
402                String jython) 
403        {
404            logger.trace("hacking bundle output!");
405            // add in some extra versioning information
406            StateManager stateManager = (StateManager)getIdv().getStateManager();
407            if (data != null) {
408                data.put(ID_MCV_VERSION, stateManager.getVersionInfo());
409            }
410            logger.trace("hacking displayControls={}", displayControls);
411            logger.trace("hacking dataSources={}", dataSources);
412            // remove ReadoutProbes from the list and possibly save off multibanded
413            // ADDE data sources
414            if (displayControls != null) {
415    //            Set<DataSourceImpl> observed = new HashSet<DataSourceImpl>();
416                Map<DataSourceImpl, List<DataChoice>> observed = new LinkedHashMap<DataSourceImpl, List<DataChoice>>();
417                List<DisplayControl> newControls = new ArrayList<DisplayControl>();
418                for (DisplayControl dc : (List<DisplayControl>)displayControls) {
419                    if (dc instanceof ReadoutProbe) {
420                        logger.trace("skipping readoutprobe!");
421                        continue;
422                    } else if (dc instanceof ImagePlanViewControl) {
423                        ImagePlanViewControl imageControl = (ImagePlanViewControl)dc;
424                        List<DataSourceImpl> tmp = (List<DataSourceImpl>)imageControl.getDataSources();
425                        for (DataSourceImpl src : tmp) {
426                            if (observed.containsKey(src)) {
427                                observed.get(src).addAll(src.getDataChoices());
428                                logger.trace("already seen src={} new selection={}", src);
429                            } else {
430                                logger.trace("haven't seen src={}", src);
431                                List<DataChoice> selected = new ArrayList<DataChoice>(imageControl.getDataChoices());
432                                observed.put(src, selected);
433                            }
434                        }
435                        logger.trace("found an image control: {} datasrcs={} datachoices={}", new Object[] { imageControl, imageControl.getDataSources(), imageControl.getDataChoices() });
436                        newControls.add(dc);
437                    } else {
438                        logger.trace("found some kinda thing: {}", dc.getClass().getName());
439                        newControls.add(dc);
440                    }
441                }
442                for (Map.Entry<DataSourceImpl, List<DataChoice>> entry : observed.entrySet()) {
443                    logger.trace("multibanded src={} choices={}", entry.getKey(), entry.getValue());
444                }
445                displayControls = newControls;
446            }
447    
448            return super.addToBundle(data, dataSources, displayControls, viewManagers, jython);
449        }
450    
451        @Override public List getLocalBundles() {
452            List<SavedBundle> allBundles = new ArrayList<SavedBundle>();
453            List<String> dirs = new ArrayList<String>();
454            String sitePath = getResourceManager().getSitePath();
455    
456            Collections.addAll(dirs, getStore().getLocalBundlesDir());
457    
458            if (sitePath != null) {
459                dirs.add(IOUtil.joinDir(sitePath, IdvObjectStore.DIR_BUNDLES));
460            }
461    
462            for (String top : dirs) {
463                List<File> subdirs = 
464                    IOUtil.getDirectories(Collections.singletonList(top), true);
465                for (File subdir : subdirs) {
466                    loadBundlesInDirectory(allBundles, 
467                        fileToCategories(top, subdir.getPath()), subdir);
468                }
469            }
470            return allBundles;
471        }
472    
473        protected void loadBundlesInDirectory(List<SavedBundle> allBundles,
474                List categories, File file) {
475            String[] localBundles = file.list();
476    
477            for (int i = 0; i < localBundles.length; i++) {
478                String filename = IOUtil.joinDir(file.toString(), localBundles[i]);
479                if (ArgumentManager.isBundle(filename)) {
480                    allBundles.add(new SavedBundle(filename,
481                        IOUtil.stripExtension(localBundles[i]), categories, true));
482                }
483            }
484        }
485    
486        /**
487         * <p>
488         * Overridden so that McIDAS-V can redirect to the version of this method
489         * that supports limiting the number of new windows.
490         * </p>
491         * 
492         * @see #decodeXml(String, boolean, String, String, boolean, boolean,
493         *      Hashtable, boolean, boolean, boolean)
494         */
495        @Override public void decodeXml(String xml, final boolean fromCollab,
496            String xmlFile, final String label, final boolean showDialog,
497            final boolean shouldMerge, final Hashtable bundleProperties,
498            final boolean removeAll, final boolean letUserChangeData) 
499        {
500            decodeXml(xml, fromCollab, xmlFile, label, showDialog, shouldMerge,
501                bundleProperties, removeAll, letUserChangeData, false);
502        }
503    
504        /**
505         * <p>
506         * Hijacks control of the IDV's bundle loading facilities. Due to the way
507         * versions of McIDAS-V prior to alpha 10 handled tabs, the user will end
508         * up with a new window for each tab in the bundle. McIDAS-V alpha 10 has
509         * the ability to only create one new window and have everything else go
510         * into that window's tabs.
511         * </p>
512         * 
513         * @see IdvPersistenceManager#decodeXmlFile(String, String, boolean, boolean, Hashtable)
514         * @see #decodeXml(String, boolean, String, String, boolean, boolean, Hashtable,
515         *      boolean, boolean, boolean)
516         */
517        @Override public boolean decodeXmlFile(String xmlFile, String label,
518                                     boolean checkToRemove,
519                                     boolean letUserChangeData,
520                                     Hashtable bundleProperties) {
521    
522            logger.trace("loading bundle: '{}'", xmlFile);
523            if (xmlFile.isEmpty()) {
524                logger.warn("attempted to open a filename that is zero characters long");
525                return false;
526            }
527            
528            String name = ((label != null) ? label : IOUtil.getFileTail(xmlFile));
529    
530            boolean shouldMerge = getStore().get(PREF_OPEN_MERGE, true);
531    
532            boolean removeAll   = false;
533    
534            boolean limitNewWindows = false;
535    
536            boolean mergeLayers = false;
537            setMergeBundledLayers(false);
538    
539            if (checkToRemove) {
540                // ok[0] = did the user press cancel 
541                boolean[] ok = getPreferenceManager().getDoRemoveBeforeOpening(name);
542    
543                if (!ok[0]) {
544                    return false;
545                }
546    
547                if (!ok[1] && !ok[2]) { // create new [opt=0]
548                    removeAll = false;
549                    shouldMerge = false;
550                    mergeLayers = false;
551                }
552                if (!ok[1] && ok[2]) { // add new tabs [opt=2]
553                    removeAll = false;
554                    shouldMerge = true;
555                    mergeLayers = false;
556                }
557                if (ok[1] && !ok[2]) { // merge with active [opt=1]
558                    removeAll = false;
559                    shouldMerge = false;
560                    mergeLayers = true;
561                }
562                if (ok[1] && ok[2]) { // replace session [opt=3]
563                    removeAll = true;
564                    shouldMerge = true;
565                    mergeLayers = false;
566                }
567    
568                logger.trace("removeAll={} shouldMerge={} mergeLayers={}", new Object[] { removeAll, shouldMerge, mergeLayers });
569    
570                setMergeBundledLayers(mergeLayers);
571    
572                if (removeAll) {
573                    // Remove the displays first because, if we remove the data 
574                    // some state can get cleared that might be accessed from a 
575                    // timeChanged on the unremoved displays
576                    getIdv().removeAllDisplays();
577                    // Then remove the data
578                    getIdv().removeAllDataSources();
579                }
580    
581                if (ok.length == 4) {
582                    limitNewWindows = ok[3];
583                }
584            }
585    
586            // the UI manager may need to know which ViewManager was active *before*
587            // we loaded the bundle.
588            lastBeforeBundle = getVMManager().getLastActiveViewManager();
589    
590            ArgumentManager argsManager = (ArgumentManager)getArgsManager();
591    
592            boolean isZidv = ArgumentManager.isZippedBundle(xmlFile);
593    
594            if (!isZidv && !ArgumentManager.isXmlBundle(xmlFile)) {
595                //If we cannot tell what it is then try to open it as a zidv file
596                try {
597                    ZipInputStream zin = 
598                        new ZipInputStream(IOUtil.getInputStream(xmlFile));
599                    isZidv = (zin.getNextEntry() != null);
600                } catch (Exception e) {}
601            }
602    
603            String bundleContents = null;
604            try {
605                //Is this a zip file
606                logger.trace("bundle file={} isZipped={}", xmlFile, ArgumentManager.isZippedBundle(xmlFile));
607                if (ArgumentManager.isZippedBundle(xmlFile)) {
608                    boolean ask   = getStore().get(PREF_ZIDV_ASK, true);
609                    boolean toTmp = getStore().get(PREF_ZIDV_SAVETOTMP, true);
610                    String  dir   = getStore().get(PREF_ZIDV_DIRECTORY, "");
611                    if (ask || ((dir.length() == 0) && !toTmp)) {
612    
613                        JCheckBox askCbx = 
614                            new JCheckBox("Don't show this again", !ask);
615    
616                        JRadioButton tmpBtn =
617                            new JRadioButton("Write to temporary directory", toTmp);
618    
619                        JRadioButton dirBtn = 
620                            new JRadioButton("Write to:", !toTmp);
621    
622                        GuiUtils.buttonGroup(tmpBtn, dirBtn);
623                        JTextField dirFld = new JTextField(dir, 30);
624                        JComponent dirComp = GuiUtils.centerRight(
625                                                dirFld,
626                                                GuiUtils.makeFileBrowseButton(
627                                                    dirFld, true, null));
628    
629                        JComponent contents =
630                            GuiUtils
631                                .vbox(GuiUtils
632                                    .inset(new JLabel("Where should the data files be written to?"),
633                                            5), tmpBtn,
634                                            GuiUtils.hbox(dirBtn, dirComp),
635                                                GuiUtils
636                                                    .inset(askCbx,
637                                                        new Insets(5, 0, 0, 0)));
638    
639                        contents = GuiUtils.inset(contents, 5);
640                        if (!GuiUtils.showOkCancelDialog(null, "Zip file data",
641                                contents, null)) {
642                            return false;
643                        }
644    
645                        ask = !askCbx.isSelected();
646    
647                        toTmp = tmpBtn.isSelected();
648    
649                        dir = dirFld.getText().toString().trim();
650    
651                        getStore().put(PREF_ZIDV_ASK, ask);
652                        getStore().put(PREF_ZIDV_SAVETOTMP, toTmp);
653                        getStore().put(PREF_ZIDV_DIRECTORY, dir);
654                        getStore().save();
655                    }
656    
657                    String tmpDir = dir;
658                    if (toTmp) {
659                        tmpDir = getIdv().getObjectStore().getUserTmpDirectory();
660                        tmpDir = IOUtil.joinDir(tmpDir, Misc.getUniqueId());
661                    }
662                    IOUtil.makeDir(tmpDir);
663    
664                    getStateManager().putProperty(PROP_ZIDVPATH, tmpDir);
665                    ZipInputStream zin =
666                        new ZipInputStream(IOUtil.getInputStream(xmlFile));
667                    ZipEntry ze = null;
668    
669                    while ((ze = zin.getNextEntry()) != null) {
670                        String entryName = ze.getName();
671    
672                        if (ArgumentManager.isXmlBundle(entryName.toLowerCase())) {
673                            bundleContents = new String(IOUtil.readBytes(zin,
674                                    null, false));
675                        } else {
676    //                        String xmlPath = IOUtil.joinDir(tmpDir, entryName);
677                            if (IOUtil.writeTo(zin, new FileOutputStream(IOUtil.joinDir(tmpDir, entryName))) < 0) {
678                                return false;
679                            }
680                        }
681                    }
682                } else {
683                    Trace.call1("Decode.readContents");
684                    bundleContents = IOUtil.readContents(xmlFile);
685                    Trace.call2("Decode.readContents");
686                }
687    
688                // TODO: this can probably go one day. I altered the prefix of the
689                // comp group classes. Old: "McIDASV...", new: "Mcv..."
690                // just gotta be sure to fix the references in the bundles.
691                // only people using the nightly build will be affected.
692                if (bundleContents != null) {
693                    bundleContents = StringUtil.substitute(bundleContents, 
694                        OLD_COMP_STUFF, NEW_COMP_STUFF);
695                    bundleContents = StringUtil.substitute(bundleContents, 
696                        OLD_SOURCE_MACRO, NEW_SOURCE_MACRO);
697                }
698                
699                
700                Trace.call1("Decode.decodeXml");
701                decodeXml(bundleContents, false, xmlFile, name, true,
702                          shouldMerge, bundleProperties, removeAll,
703                          letUserChangeData, limitNewWindows);
704                Trace.call2("Decode.decodeXml");
705                return true;
706            } catch (Throwable exc) {
707                if (contents == null) {
708                    logException("Unable to load bundle:" + xmlFile, exc);
709                } else {
710                    logException("Unable to evaluate bundle:" + xmlFile, exc);
711                }
712                return false;
713            }
714        }
715    
716        // replace "old" references in a bundle's XML to the "new" classes.
717        private static final String OLD_COMP_STUFF = "McIDASVComp";
718        private static final String NEW_COMP_STUFF = "McvComp";
719    
720        private static final String OLD_SOURCE_MACRO = "%fulldatasourcename%";
721        private static final String NEW_SOURCE_MACRO = "%datasourcename%";
722    
723        /**
724         * <p>Overridden so that McIDAS-V can redirect to the version of this 
725         * method that supports limiting the number of new windows.</p>
726         * 
727         * @see #decodeXmlInner(String, boolean, String, String, boolean, boolean, Hashtable, boolean, boolean, boolean)
728         */
729        @Override protected synchronized void decodeXmlInner(String xml,
730                                                             boolean fromCollab, 
731                                                             String xmlFile, 
732                                                             String label, 
733                                                             boolean showDialog, 
734                                                             boolean shouldMerge, 
735                                                             Hashtable bundleProperties,
736                                                             boolean didRemoveAll, 
737                                                             boolean changeData) {
738    
739            decodeXmlInner(xml, fromCollab, xmlFile, label, showDialog, 
740                          shouldMerge, bundleProperties, didRemoveAll, changeData, 
741                          false);
742    
743        }
744    
745        /**
746         * <p>
747         * Overridden so that McIDAS-V can redirect to the version of this method
748         * that supports limiting the number of new windows.
749         * </p>
750         * 
751         * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog,
752         *      boolean, Hashtable, boolean, boolean, boolean)
753         */
754        @Override protected void instantiateFromBundle(Hashtable ht,
755            boolean fromCollab, LoadBundleDialog loadDialog, boolean shouldMerge,
756            Hashtable bundleProperties, boolean didRemoveAll,
757            boolean letUserChangeData) throws Exception 
758        {
759            instantiateFromBundle(ht, fromCollab, loadDialog, shouldMerge,
760                bundleProperties, didRemoveAll, letUserChangeData, false);
761        }
762    
763        /**
764         * <p>
765         * Hijacks the second part of the IDV bundle loading pipeline so that
766         * McIDAS-V can limit the number of new windows.
767         * </p>
768         * 
769         * @see IdvPersistenceManager#decodeXml(String, boolean,
770         *      String, String, boolean, boolean, Hashtable, boolean, boolean)
771         * @see #decodeXmlInner(String, boolean, String, String, boolean, boolean,
772         *      Hashtable, boolean, boolean, boolean)
773         */
774        public void decodeXml(final String xml, final boolean fromCollab,
775            final String xmlFile, final String label, final boolean showDialog,
776            final boolean shouldMerge, final Hashtable bundleProperties,
777            final boolean removeAll, final boolean letUserChangeData,
778            final boolean limitWindows) 
779        {
780    
781            if (!getStateManager().getShouldLoadBundlesSynchronously()) {
782                Runnable runnable = new Runnable() {
783    
784                    public void run() {
785                        decodeXmlInner(xml, fromCollab, xmlFile, label,
786                            showDialog, shouldMerge, bundleProperties, removeAll,
787                            letUserChangeData, limitWindows);
788                    }
789                };
790                Misc.run(runnable);
791            } else {
792                decodeXmlInner(xml, fromCollab, xmlFile, label, showDialog,
793                    shouldMerge, bundleProperties, removeAll, letUserChangeData,
794                    limitWindows);
795            }
796        }
797        
798        /**
799         * <p>Hijacks the third part of the bundle loading pipeline.</p>
800         * 
801         * @see IdvPersistenceManager#decodeXmlInner(String, boolean, String, String, boolean, boolean, Hashtable, boolean, boolean)
802         * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean, boolean)
803         */
804        protected synchronized void decodeXmlInner(String xml, boolean fromCollab, 
805                                                   String xmlFile, String label,
806                                                   boolean showDialog, 
807                                                   boolean shouldMerge, 
808                                                   Hashtable bundleProperties, 
809                                                   boolean didRemoveAll, 
810                                                   boolean letUserChangeData, 
811                                                   boolean limitNewWindows) {
812                                                   
813            LoadBundleDialog loadDialog = new LoadBundleDialog(this, label);
814            
815            boolean inError = false;
816            
817            if ( !fromCollab) {
818                showWaitCursor();
819                if (showDialog) {
820                    loadDialog.showDialog();
821                }
822            }
823            
824            if (xmlFile != null) {
825                getStateManager().putProperty(PROP_BUNDLEPATH,
826                                              IOUtil.getFileRoot(xmlFile));
827            }
828            
829            getStateManager().putProperty(PROP_LOADINGXML, true);
830            try {
831                xml = applyPropertiesToBundle(xml);
832                if (xml == null) {
833                    return;
834                }
835                
836    //            checkForBadMaps(xmlFile);
837                // perform any URL remapping that might be needed
838                ServerUrlRemapper remapper = new ServerUrlRemapper(getIdv());
839                Element bundleRoot = remapper.remapUrlsInBundle(xml);
840                if (bundleRoot == null) {
841                    return;
842                }
843                
844                remapper = null;
845                
846                Trace.call1("Decode.toObject");
847                Object data = getIdv().getEncoderForRead().toObject(bundleRoot);
848                Trace.call2("Decode.toObject");
849                
850                if (data != null) {
851                    Hashtable properties = new Hashtable();
852                    if (data instanceof Hashtable) {
853                        Hashtable ht = (Hashtable) data;
854                        
855                        instantiateFromBundle(ht, fromCollab, loadDialog,
856                                              shouldMerge, bundleProperties,
857                                              didRemoveAll, letUserChangeData, 
858                                              limitNewWindows);
859                                              
860                    } else if (data instanceof DisplayControl) {
861                        ((DisplayControl) data).initAfterUnPersistence(getIdv(),
862                                                                       properties);
863                        loadDialog.addDisplayControl((DisplayControl) data);
864                    } else if (data instanceof DataSource) {
865                        getIdv().getDataManager().addDataSource((DataSource)data);
866                    } else if (data instanceof ColorTable) {
867                        getColorTableManager().doImport(data, true);
868                    } else {
869                        LogUtil.userErrorMessage(log_,
870                                                 "Decoding xml. Unknown object type:"
871                                                 + data.getClass().getName());
872                    }
873                    
874                    if ( !fromCollab && getIdv().haveCollabManager()) {
875                        getCollabManager().write(getCollabManager().MSG_BUNDLE,
876                                                 xml);
877                    }
878                }
879            } catch (Throwable exc) {
880                if (xmlFile != null) {
881                    logException("Error loading bundle: " + xmlFile, exc);
882                } else {
883                    logException("Error loading bundle", exc);
884                }
885                
886                inError = true;
887            }
888            
889            if (!fromCollab) {
890                showNormalCursor();
891            }
892            
893            getStateManager().putProperty(PROP_BUNDLEPATH, "");
894            getStateManager().putProperty(PROP_ZIDVPATH, "");
895            getStateManager().putProperty(PROP_LOADINGXML, false);
896            
897            if (!inError && getIdv().getInteractiveMode() && xmlFile != null) {
898                getIdv().addToHistoryList(xmlFile);
899            }
900            
901            loadDialog.dispose();
902            if (loadDialog.getShouldRemoveItems()) {
903                List displayControls = loadDialog.getDisplayControls();
904                for (int i = 0; i < displayControls.size(); i++) {
905                    try {
906                        ((DisplayControl) displayControls.get(i)).doRemove();
907                    } catch (Exception exc) {
908                        logger.warn("unexpected exception={}", exc);
909                    }
910                }
911                List dataSources = loadDialog.getDataSources();
912                for (int i = 0; i < dataSources.size(); i++) {
913                    getIdv().removeDataSource((DataSource) dataSources.get(i));
914                }
915            }
916            
917            loadDialog.clear();
918        }
919        
920        // initial pass at trying to fix bundles with resources mcv hasn't heard of
921        private void checkForBadMaps(final String bundlePath) {
922            String xpath = "//property[@name=\"InitialMap\"]/string|//property[@name=\"MapStates\"]//property[@name=\"Source\"]/string";
923            for (Node node : XPathUtils.nodes(bundlePath, xpath)) {
924                String mapPath = node.getTextContent();
925                if (mapPath.contains("_dir/")) { // hahaha this needs some work
926                    List<String> toks = StringUtil.split(mapPath, "_dir/");
927                    if (toks.size() == 2) {
928                        String plugin = toks.get(0).replace("/", "");
929                        logger.trace("plugin: {} map: {}", plugin, mapPath);
930                    }
931                } else {
932                    logger.trace("normal map: {}", mapPath);
933                }
934            }
935        }
936    
937        /**
938         * <p>
939         * Builds a list of an incoming bundle's
940         * {@link ucar.unidata.idv.ViewManager}s that are part of a component
941         * group.
942         * </p>
943         * 
944         * <p>
945         * The reason for only being interested in component groups is because any
946         * windows <i>not</i> using component groups will be made into a dynamic
947         * skin. The associated ViewManagers do not technically exist until the
948         * skin has been &quot;built&quot;, so there's nothing to do. These
949         * ViewManagers must also be removed from the bundle's list of
950         * ViewManagers.
951         * </p>
952         * 
953         * <p>
954         * However, any ViewManagers associated with component groups still need to
955         * appear in the bundle's ViewManager list, and that's where this method
956         * comes into play!
957         * </p>
958         * 
959         * @param windows WindowInfos to be searched.
960         * 
961         * @return List of ViewManagers inside any component groups.
962         */
963        protected static List<ViewManager> extractCompGroupVMs(
964            final List<WindowInfo> windows) 
965        {
966    
967            List<ViewManager> newList = new ArrayList<ViewManager>();
968    
969            for (WindowInfo window : windows) {
970                Collection<Object> comps =
971                    window.getPersistentComponents().values();
972    
973                for (Object comp : comps) {
974                    if (!(comp instanceof IdvComponentGroup)) {
975                        continue;
976                    }
977    
978                    IdvComponentGroup group = (IdvComponentGroup)comp;
979                    List<IdvComponentHolder> holders =
980                        group.getDisplayComponents();
981    
982                    for (IdvComponentHolder holder : holders) {
983                        if (holder.getViewManagers() != null) {
984                            logger.trace("extracted: {}", holder.getViewManagers().size());
985                            newList.addAll(holder.getViewManagers());
986                        }
987                    }
988                }
989            }
990            return newList;
991        }
992    
993        /**
994         * <p>Does the work in fixing the collisions described in the
995         * <code>instantiateFromBundle</code> javadoc. Basically just queries the
996         * {@link ucar.unidata.idv.VMManager} for each 
997         * {@link ucar.unidata.idv.ViewManager}. If a match is found, a new ID is
998         * generated and associated with the ViewManager, its 
999         * {@link ucar.unidata.idv.ViewDescriptor}, and any associated 
1000         * {@link ucar.unidata.idv.DisplayControl}s.</p>
1001         * 
1002         * @param vms ViewManagers in the incoming bundle.
1003         * 
1004         * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean, boolean)
1005         */
1006        protected void reverseCollisions(final List<ViewManager> vms) {
1007            for (ViewManager vm : vms) {
1008                ViewDescriptor vd = vm.getViewDescriptor();
1009                ViewManager current = getVMManager().findViewManager(vd);
1010                if (current != null) {
1011                    ViewDescriptor oldVd = current.getViewDescriptor();
1012                    String oldId = oldVd.getName();
1013                    String newId = "view_" + Misc.getUniqueId();
1014    
1015                    oldVd.setName(newId);
1016                    current.setUniqueId(newId);
1017    
1018                    List<DisplayControlImpl> controls = current.getControls();
1019                    for (DisplayControlImpl control : controls) {
1020                        control.resetViewManager(oldId, newId);
1021                    }
1022                }
1023            }
1024        }
1025    
1026        /**
1027         * <p>Builds a single window with a single component group. The group 
1028         * contains component holders that correspond to each window or component
1029         * holder stored in the incoming bundle.</p>
1030         * 
1031         * @param windows The bundle's list of 
1032         *                {@link ucar.unidata.idv.ui.WindowInfo}s.
1033         * 
1034         * @return List of WindowInfos that contains only one element/window.
1035         * 
1036         * @throws Exception Bubble up any exceptions from 
1037         *                   <code>makeImpromptuSkin</code>.
1038         */
1039        protected List<WindowInfo> injectComponentGroups(
1040            final List<WindowInfo> windows) throws Exception {
1041    
1042            McvComponentGroup group = 
1043                new McvComponentGroup(getIdv(), "Group");
1044    
1045            group.setLayout(McvComponentGroup.LAYOUT_TABS);
1046    
1047            Hashtable<String, McvComponentGroup> persist = 
1048                new Hashtable<String, McvComponentGroup>();
1049    
1050            for (WindowInfo window : windows) {
1051                List<IdvComponentHolder> holders = buildHolders(window);
1052                for (IdvComponentHolder holder : holders)
1053                    group.addComponent(holder);
1054            }
1055    
1056            persist.put("comp1", group);
1057    
1058            // build a new window that contains our component group.
1059            WindowInfo limitedWindow = new WindowInfo();
1060            limitedWindow.setPersistentComponents(persist);
1061            limitedWindow.setSkinPath(Constants.BLANK_COMP_GROUP);
1062            limitedWindow.setIsAMainWindow(true);
1063            limitedWindow.setTitle("Super Test");
1064            limitedWindow.setViewManagers(new ArrayList<ViewManager>());
1065            limitedWindow.setBounds(windows.get(0).getBounds());
1066    
1067            // make a new list so that we can populate the list of windows with 
1068            // our single window.
1069            List<WindowInfo> newWindow = new ArrayList<WindowInfo>();
1070            newWindow.add(limitedWindow);
1071            return newWindow;
1072        }
1073    
1074        /**
1075         * <p>
1076         * Builds an altered copy of <code>windows</code> that preserves the
1077         * number of windows while ensuring all displays are inside component
1078         * holders.
1079         * </p>
1080         * 
1081         * @throws Exception Bubble up dynamic skin exceptions.
1082         * 
1083         * @see #injectComponentGroups(List)
1084         */
1085        // TODO: better name!!
1086        protected List<WindowInfo> betterInject(final List<WindowInfo> windows)
1087            throws Exception 
1088        {
1089    
1090            List<WindowInfo> newList = new ArrayList<WindowInfo>();
1091    
1092            for (WindowInfo window : windows) {
1093                McvComponentGroup group = new McvComponentGroup(getIdv(), "Group");
1094    
1095                group.setLayout(McvComponentGroup.LAYOUT_TABS);
1096    
1097                Hashtable<String, McvComponentGroup> persist =
1098                    new Hashtable<String, McvComponentGroup>();
1099    
1100                List<IdvComponentHolder> holders = buildHolders(window);
1101                for (IdvComponentHolder holder : holders) {
1102                    group.addComponent(holder);
1103                }
1104    
1105                persist.put("comp1", group);
1106                WindowInfo newWindow = new WindowInfo();
1107                newWindow.setPersistentComponents(persist);
1108                newWindow.setSkinPath(Constants.BLANK_COMP_GROUP);
1109                newWindow.setIsAMainWindow(window.getIsAMainWindow());
1110                newWindow.setViewManagers(new ArrayList<ViewManager>());
1111                newWindow.setBounds(window.getBounds());
1112    
1113                newList.add(newWindow);
1114            }
1115            return newList;
1116        }
1117    
1118        /**
1119         * <p>Builds a list of component holders with all of <code>window</code>'s
1120         * displays.</p>
1121         * 
1122         * @throws Exception Bubble up any problems creating a dynamic skin.
1123         */
1124        // TODO: refactor
1125        protected List<IdvComponentHolder> buildHolders(final WindowInfo window) 
1126            throws Exception {
1127    
1128            List<IdvComponentHolder> holders = 
1129                new ArrayList<IdvComponentHolder>();
1130    
1131            if (!window.getPersistentComponents().isEmpty()) {
1132                Collection<Object> comps = 
1133                    window.getPersistentComponents().values();
1134    
1135                for (Object comp : comps) {
1136                    if (!(comp instanceof IdvComponentGroup)) {
1137                        continue;
1138                    }
1139    
1140                    IdvComponentGroup group = (IdvComponentGroup)comp;
1141                    holders.addAll(McVGuiUtils.getComponentHolders(group));
1142                }
1143            } else {
1144                holders.add(makeDynSkin(window));
1145            }
1146    
1147            return holders;
1148        }
1149    
1150        /**
1151         * <p>Builds a list of any dynamic skins in the bundle and adds them to the
1152         * UIMananger's &quot;cache&quot; of encountered ViewManagers.</p>
1153         * 
1154         * @param windows The bundle's windows.
1155         * 
1156         * @return Any dynamic skins in <code>windows</code>.
1157         */
1158        public List<ViewManager> mapDynamicSkins(final List<WindowInfo> windows) {
1159            List<ViewManager> vms = new ArrayList<ViewManager>();
1160            for (WindowInfo window : windows) {
1161                Collection<Object> comps = 
1162                    window.getPersistentComponents().values();
1163    
1164                for (Object comp : comps) {
1165                    if (!(comp instanceof IdvComponentGroup)) {
1166                        continue;
1167                    }
1168    
1169                    List<IdvComponentHolder> holders = 
1170                        new ArrayList<IdvComponentHolder>(
1171                                ((IdvComponentGroup)comp).getDisplayComponents());
1172    
1173                    for (IdvComponentHolder holder : holders) {
1174                        if (!McVGuiUtils.isDynamicSkin(holder)) {
1175                            continue;
1176                        }
1177                        List<ViewManager> tmpvms = holder.getViewManagers();
1178                        for (ViewManager vm : tmpvms) {
1179                            vms.add(vm);
1180                            UIManager.savedViewManagers.put(
1181                                vm.getViewDescriptor().getName(), vm);
1182                        }
1183                        holder.setViewManagers(new ArrayList<ViewManager>());
1184                    }
1185                }
1186            }
1187            return vms;
1188        }
1189    
1190        /**
1191         * Attempts to reconcile McIDAS-V's ability to easily load all files in a
1192         * directory with the way the IDV expects file data sources to behave upon
1193         * unpersistence.
1194         * 
1195         * <p>The problem is twofold: the paths referenced in the data source's 
1196         * {@code Sources} may not exist, and the <i>persistence</i> code combines
1197         * each individual file into a blob.
1198         * 
1199         * <p>The current solution is to note that the data source's 
1200         * {@link PollingInfo} is used by {@link ucar.unidata.data.FilesDataSource#initWithPollingInfo}
1201         * to replace the contents of the data source's file paths. Simply 
1202         * overwrite {@code PollingInfo#filePaths} with the path to the blob.
1203         * 
1204         * @param ds {@code List} of {@link DataSourceImpl}s to inspect and/or fix.
1205         * Cannot be {@code null}.
1206         * 
1207         * @see #isBulkDataSource(DataSourceImpl)
1208         */
1209        private void fixBulkDataSources(final List<DataSourceImpl> ds) {
1210            String zidvPath = getStateManager().getProperty(PROP_ZIDVPATH, "");
1211    
1212            // bail out if the macro replacement cannot work
1213            if (zidvPath.length() == 0) {
1214                return;
1215            }
1216    
1217            for (DataSourceImpl d : ds) {
1218                boolean isBulk = isBulkDataSource(d);
1219                if (!isBulk) {
1220                    continue;
1221                }
1222    
1223                // err... now do the macro sub and replace the contents of 
1224                // data paths with the singular element in temp paths?
1225                List<String> tempPaths = new ArrayList<String>(d.getTmpPaths());
1226                String tempPath = tempPaths.get(0);
1227                tempPath = tempPath.replace(MACRO_ZIDVPATH, zidvPath);
1228                tempPaths.set(0, tempPath);
1229                PollingInfo p = d.getPollingInfo();
1230                p.setFilePaths(tempPaths);
1231            }
1232        }
1233    
1234        /**
1235         * Attempts to determine whether or not a given {@link DataSourceImpl} is
1236         * the result of a McIDAS-V {@literal "bulk load"}.
1237         * 
1238         * @param d {@code DataSourceImpl} to check. Cannot be {@code null}.
1239         * 
1240         * @return {@code true} if the {@code DataSourceImpl} matched the criteria.
1241         */
1242        private boolean isBulkDataSource(final DataSourceImpl d) {
1243            Hashtable properties = d.getProperties();
1244            if (properties.containsKey("bulk.load")) {
1245                // woohoo! no need to do the guesswork.
1246                Object value = properties.get("bulk.load");
1247                if (value instanceof String) {
1248                    return Boolean.valueOf((String)value);
1249                } else if (value instanceof Boolean) {
1250                    return (Boolean)value;
1251                }
1252            }
1253    
1254            DataSourceDescriptor desc = d.getDescriptor();
1255            boolean localFiles = desc.getFileSelection();
1256    
1257            List filePaths = d.getDataPaths();
1258            List tempPaths = d.getTmpPaths();
1259            if (filePaths == null || filePaths.isEmpty()) {
1260                return false;
1261            }
1262    
1263            if (tempPaths == null || tempPaths.isEmpty()) {
1264                return false;
1265            }
1266    
1267            // the least-involved heuristic i've found is:
1268            // localFiles == true
1269            // tempPaths.size() == 1 && filePaths.size() >= 2
1270            // and then we have a bulk load...
1271            // if those checks don't suffice, you can also look for the "prop.pollinfo" key
1272            // if the PollingInfo object has a filePaths list, with one element whose last directory matches 
1273            // the data source "name" (then you are probably good).
1274            if ((localFiles == true) && ((tempPaths.size() == 1) && (filePaths.size() >= 2))) {
1275                return true;
1276            }
1277    
1278            // end of line
1279            return false;
1280        }
1281    
1282        /**
1283         * <p>Overridden so that McIDAS-V can preempt the IDV's bundle loading. 
1284         * There will be problems if any of the incoming 
1285         * {@link ucar.unidata.idv.ViewManager}s share an ID with an existing 
1286         * ViewManager. While this case may seem unlikely, it can be triggered 
1287         * when loading a bundle and then reloading. The problem is that the 
1288         * ViewManagers are the same, and if the previous ViewManagers were not 
1289         * removed, the IDV doesn't know what to do.</p>
1290         * 
1291         * <p>Assigning the incoming ViewManagers a new ID, <i>and associating its
1292         * {@link ucar.unidata.idv.ViewDescriptor}s and 
1293         * {@link ucar.unidata.idv.DisplayControl}s</i> with the new ID fixes this
1294         * problem.</p>
1295         * 
1296         * <p>McIDAS-V also allows the user to limit the number of new windows the
1297         * bundle may create. If enabled, one new window will be created, and any
1298         * additional windows will become tabs (component holders) inside the new
1299         * window.</p>
1300         * 
1301         * <p>McIDAS-V also prefers the bundles being loaded to be in a 
1302         * semi-regular regular state. For example, say you have bundle containing
1303         * only data. The bundle will probably not contain lists of WindowInfos or
1304         * ViewManagers. Perhaps the bundle contains nested component groups as 
1305         * well! McIDAS-V will alter the unpersisted bundle state (<i>not the 
1306         * actual bundle file</i>) to make it fit into the expected idiom. Mostly
1307         * this just entails wrapping things in component groups and holders while
1308         * &quot;flattening&quot; any nested component groups.</p>
1309         * 
1310         * @param ht Holds unpersisted objects.
1311         * 
1312         * @param fromCollab Did the bundle come from the collab stuff?
1313         * 
1314         * @param loadDialog Show the bundle loading dialog?
1315         * 
1316         * @param shouldMerge Merge bundle contents into an existing window?
1317         * 
1318         * @param bundleProperties If non-null, use the set of time indices for 
1319         *                         data sources?
1320         * 
1321         * @param didRemoveAll Remove all data and displays?
1322         * 
1323         * @param letUserChangeData Allow changes to the data path?
1324         * 
1325         * @param limitNewWindows Only create one new window?
1326         * 
1327         * @see IdvPersistenceManager#instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean)
1328         */
1329        // TODO: check the accuracy of the bundleProperties javadoc above
1330        protected void instantiateFromBundle(Hashtable ht, 
1331                                             boolean fromCollab,
1332                                             LoadBundleDialog loadDialog,
1333                                             boolean shouldMerge,
1334                                             Hashtable bundleProperties,
1335                                             boolean didRemoveAll,
1336                                             boolean letUserChangeData,
1337                                             boolean limitNewWindows) 
1338                throws Exception {
1339    
1340            // hacky way of allowing other classes to determine whether or not
1341            // a bundle is loading
1342            bundleLoading = true;
1343    
1344            // every bundle should have lists corresponding to these ids
1345            final String[] important = { 
1346                ID_VIEWMANAGERS, ID_DISPLAYCONTROLS, ID_WINDOWS,
1347            };
1348            populateEssentialLists(important, ht);
1349    
1350            List<ViewManager> vms = (List)ht.get(ID_VIEWMANAGERS);
1351            List<DisplayControlImpl> controls = (List)ht.get(ID_DISPLAYCONTROLS);
1352            List<WindowInfo> windows = (List)ht.get(ID_WINDOWS);
1353    
1354            List<DataSourceImpl> dataSources = (List)ht.get("datasources");
1355            if (dataSources != null) {
1356                fixBulkDataSources(dataSources);
1357            }
1358    
1359            // older hydra bundles may contain ReadoutProbes in the list of
1360            // display controls. these are not needed, so they get removed.
1361    //        controls = removeReadoutProbes(controls);
1362            ht.put(ID_DISPLAYCONTROLS, controls);
1363    
1364            if (vms.isEmpty() && windows.isEmpty() && !controls.isEmpty()) {
1365                List<ViewManager> fudged = generateViewManagers(controls);
1366                List<WindowInfo> buh = wrapViewManagers(fudged);
1367    
1368                windows.addAll(buh);
1369                vms.addAll(fudged);
1370            }
1371    
1372            // make sure that the list of windows contains no nested comp groups
1373            flattenWindows(windows);
1374    
1375            // remove any component holders that don't contain displays
1376            windows = removeUIHolders(windows);
1377    
1378            // generate new IDs for any collisions--typically happens if the same
1379            // bundle is loaded without removing the previously loaded VMs.
1380            reverseCollisions(vms);
1381    
1382            // if the incoming bundle has dynamic skins, we've gotta be sure to
1383            // remove their ViewManagers from the bundle's list of ViewManagers!
1384            // remember, because they are dynamic skins, the ViewManagers should
1385            // not exist until the skin is built.
1386            if (McVGuiUtils.hasDynamicSkins(windows)) {
1387                mapDynamicSkins(windows);
1388            }
1389    
1390            List<WindowInfo> newWindows;
1391            if (limitNewWindows && windows.size() > 1) {
1392                newWindows = injectComponentGroups(windows);
1393            } else {
1394                newWindows = betterInject(windows);
1395            }
1396    
1397    //          if (limitNewWindows && windows.size() > 1) {
1398    //              // make a single new window with a single component group. 
1399    //              // the group's holders will correspond to each window in the 
1400    //              // bundle.
1401    //              List<WindowInfo> newWindows = injectComponentGroups(windows);
1402    //              ht.put(ID_WINDOWS, newWindows);
1403    //
1404    //              // if there are any component groups in the bundle, we must 
1405    //              // take care that their VMs appear in this list. VMs wrapped 
1406    //              // in dynamic skins don't "exist" at this point, so they do 
1407    //              // not need to be in this list.
1408    //              ht.put(ID_VIEWMANAGERS, extractCompGroupVMs(newWindows));
1409    //          }
1410    
1411            ht.put(ID_WINDOWS, newWindows);
1412    
1413            ht.put(ID_VIEWMANAGERS, extractCompGroupVMs(newWindows));
1414    
1415            // hand our modified bundle information off to the IDV
1416            super.instantiateFromBundle(ht, fromCollab, loadDialog, shouldMerge, 
1417                                        bundleProperties, didRemoveAll, 
1418                                        letUserChangeData);
1419    
1420            // no longer needed; the bundle is done loading.
1421            UIManager.savedViewManagers.clear();
1422            bundleLoading = false;
1423        }
1424    
1425    //    private List<DisplayControlImpl> removeReadoutProbes(final List<DisplayControlImpl> controls) {
1426    //        List<DisplayControlImpl> filtered = new ArrayList<DisplayControlImpl>();
1427    //        for (DisplayControlImpl dc : controls) {
1428    //            if (dc instanceof ReadoutProbe) {
1429    //                try {
1430    //                    dc.doRemove();
1431    //                } catch (Exception e) {
1432    //                    LogUtil.logException("Problem removing redundant readout probe", e);
1433    //                }
1434    //            } else if (dc != null) {
1435    //                filtered.add(dc);
1436    //            }
1437    //        }
1438    //        return filtered;
1439    //    }
1440    
1441        private List<WindowInfo> wrapViewManagers(final List<ViewManager> vms) {
1442            List<WindowInfo> windows = new ArrayList<WindowInfo>();
1443            for (ViewManager vm : vms) {
1444                WindowInfo window = new WindowInfo();
1445                window.setIsAMainWindow(true);
1446                window.setSkinPath("/ucar/unidata/idv/resources/skins/skin.xml");
1447                window.setTitle("asdf");
1448                List<ViewManager> vmList = new ArrayList<ViewManager>();
1449                vmList.add(vm);
1450                window.setViewManagers(vmList);
1451                window.setBounds(new Rectangle(200, 200, 200, 200));
1452                windows.add(window);
1453            }
1454            return windows;
1455        }
1456    
1457        private List<ViewManager> generateViewManagers(final List<DisplayControlImpl> controls) {
1458            List<ViewManager> vms = new ArrayList<ViewManager>();
1459            for (DisplayControlImpl control : controls) {
1460                ViewManager vm = getVMManager().findOrCreateViewManager(control.getDefaultViewDescriptor(), "");
1461                vms.add(vm);
1462            }
1463            return vms;
1464        }
1465    
1466        /**
1467         * <p>Alters <code>windows</code> so that no windows in the bundle contain
1468         * nested component groups.</p>
1469         */
1470        protected void flattenWindows(final List<WindowInfo> windows) {
1471            for (WindowInfo window : windows) {
1472                Map<String, Object> persist = window.getPersistentComponents();
1473                Set<Map.Entry<String, Object>> blah = persist.entrySet();
1474                for (Map.Entry<String, Object> entry : blah) {
1475                    if (!(entry.getValue() instanceof IdvComponentGroup)) {
1476                        continue;
1477                    }
1478    
1479                    IdvComponentGroup group = (IdvComponentGroup)entry.getValue();
1480                    if (McVGuiUtils.hasNestedGroups(group)) {
1481                        entry.setValue(flattenGroup(group));
1482                    }
1483                }
1484            }
1485        }
1486    
1487        /**
1488         * @return An altered version of <code>nested</code> that contains no 
1489         *         nested component groups.
1490         */
1491        protected IdvComponentGroup flattenGroup(final IdvComponentGroup nested) {
1492            IdvComponentGroup flat = 
1493                new IdvComponentGroup(getIdv(), nested.getName());
1494    
1495            flat.setLayout(nested.getLayout());
1496            flat.setShowHeader(nested.getShowHeader());
1497            flat.setUniqueId(nested.getUniqueId());
1498    
1499            List<IdvComponentHolder> holders = 
1500                McVGuiUtils.getComponentHolders(nested);
1501    
1502            for (IdvComponentHolder holder : holders) {
1503                flat.addComponent(holder);
1504                holder.setParent(flat);
1505            }
1506    
1507            return flat;
1508        }
1509    
1510        /**
1511         * @return An altered <code>group</code> containing only component holders
1512         *         with displays.
1513         */
1514        protected static List<IdvComponentHolder> removeUIHolders(final IdvComponentGroup group) {
1515            List<IdvComponentHolder> newHolders = 
1516                new ArrayList<IdvComponentHolder>(group.getDisplayComponents());
1517    
1518            for (IdvComponentHolder holder : newHolders) {
1519                if (McVGuiUtils.isUIHolder(holder)) {
1520                    newHolders.remove(holder);
1521                }
1522            }
1523    
1524            return newHolders;
1525        }
1526    
1527        /**
1528         * <p>Ensures that the lists corresponding to the ids in <code>ids</code>
1529         * actually exist in <code>table</code>, even if they are empty.</p>
1530         */
1531        // TODO: not a fan of this method.
1532        protected static void populateEssentialLists(final String[] ids, final Hashtable<String, Object> table) {
1533            for (String id : ids) {
1534                if (table.get(id) == null) {
1535                    table.put(id, new ArrayList<Object>());
1536                }
1537            }
1538        }
1539    
1540        /**
1541         * <p>Returns an altered copy of <code>windows</code> containing only 
1542         * component holders that have displays.</p>
1543         * 
1544         * <p>The IDV allows users to embed HTML controls or things like the 
1545         * dashboard into component holders. This ability, while powerful, could
1546         * make for a confusing UI.</p>
1547         */
1548        protected static List<WindowInfo> removeUIHolders(
1549            final List<WindowInfo> windows) {
1550    
1551            List<WindowInfo> newList = new ArrayList<WindowInfo>();
1552            for (WindowInfo window : windows) {
1553                // TODO: ought to write a WindowInfo cloning method
1554                WindowInfo newWin = new WindowInfo();
1555                newWin.setViewManagers(window.getViewManagers());
1556                newWin.setSkinPath(window.getSkinPath());
1557                newWin.setIsAMainWindow(window.getIsAMainWindow());
1558                newWin.setBounds(window.getBounds());
1559                newWin.setTitle(window.getTitle());
1560    
1561                Hashtable<String, IdvComponentGroup> persist = 
1562                    new Hashtable<String, IdvComponentGroup>(
1563                        window.getPersistentComponents()); 
1564    
1565                for (Map.Entry<String, IdvComponentGroup> e : persist.entrySet()) {
1566    
1567                    IdvComponentGroup g = e.getValue();
1568    
1569                    List<IdvComponentHolder> holders = g.getDisplayComponents();
1570                    if (holders == null || holders.isEmpty()) {
1571                        continue;
1572                    }
1573    
1574                    List<IdvComponentHolder> newHolders = 
1575                        new ArrayList<IdvComponentHolder>();
1576    
1577                    // filter out any holders that don't contain view managers
1578                    for (IdvComponentHolder holder : holders) {
1579                        if (!McVGuiUtils.isUIHolder(holder)) {
1580                            newHolders.add(holder);
1581                        }
1582                    }
1583    
1584                    g.setDisplayComponents(newHolders);
1585                }
1586    
1587                newWin.setPersistentComponents(persist);
1588                newList.add(newWin);
1589            }
1590            return newList;
1591        }
1592    
1593        /**
1594         * <p>Uses the {@link ucar.unidata.idv.ViewManager}s in <code>info</code> 
1595         * to build a dynamic skin.</p>
1596         * 
1597         * @param info Window that needs to become a dynamic skin.
1598         * 
1599         * @return A {@link edu.wisc.ssec.mcidasv.ui.McvComponentHolder} containing 
1600         *         the ViewManagers inside <code>info</code>.
1601         * 
1602         * @throws Exception Bubble up any XML problems.
1603         */
1604        public McvComponentHolder makeDynSkin(final WindowInfo info) throws Exception {
1605            Document doc = XmlUtil.getDocument(SIMPLE_SKIN_TEMPLATE);
1606            Element root = doc.getDocumentElement();
1607    
1608            Element panel = XmlUtil.findElement(root, DYNSKIN_TAG_PANEL,
1609                                                DYNSKIN_ATTR_ID, DYNSKIN_ID_VALUE);
1610    
1611            List<ViewManager> vms = info.getViewManagers();
1612    
1613            panel.setAttribute(DYNSKIN_ATTR_COLS, Integer.toString(vms.size()));
1614    
1615            for (ViewManager vm : vms) {
1616    
1617                Element view = doc.createElement(DYNSKIN_TAG_VIEW);
1618    
1619                view.setAttribute(DYNSKIN_ATTR_CLASS, vm.getClass().getName());
1620                view.setAttribute(DYNSKIN_ATTR_VIEWID, vm.getUniqueId());
1621    
1622                StringBuffer props = new StringBuffer(DYNSKIN_PROPS_GENERAL);
1623    
1624                if (vm instanceof MapViewManager) {
1625                    if (((MapViewManager)vm).getUseGlobeDisplay()) {
1626                        props.append(DYNSKIN_PROPS_GLOBE);
1627                    }
1628                }
1629    
1630                view.setAttribute(DYNSKIN_ATTR_PROPS, props.toString());
1631    
1632                panel.appendChild(view);
1633    
1634                UIManager.savedViewManagers.put(vm.getViewDescriptor().getName(), vm);
1635            }
1636    
1637            McvComponentHolder holder = 
1638                new McvComponentHolder(getIdv(), XmlUtil.toString(root));
1639    
1640            holder.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN);
1641            holder.setName(DYNSKIN_TMPNAME);
1642            holder.doMakeContents();
1643            return holder;
1644        }
1645    
1646        public static IdvWindow buildDynamicSkin(int width, int height, int rows, int cols, List<PyObject> panelTypes) throws Exception {
1647            Document doc = XmlUtil.getDocument(SIMPLE_SKIN_TEMPLATE);
1648            Element root = doc.getDocumentElement();
1649            Element panel = XmlUtil.findElement(root, DYNSKIN_TAG_PANEL, DYNSKIN_ATTR_ID, DYNSKIN_ID_VALUE);
1650            panel.setAttribute(DYNSKIN_ATTR_ROWS, Integer.toString(rows));
1651            panel.setAttribute(DYNSKIN_ATTR_COLS, Integer.toString(cols));
1652            Element view = doc.createElement(DYNSKIN_TAG_VIEW);
1653            for (PyObject panelType : panelTypes) {
1654                String panelTypeRepr = panelType.__repr__().toString();
1655                Element node = doc.createElement(IdvUIManager.COMP_VIEW);
1656                StringBuilder props = new StringBuilder(DYNSKIN_PROPS_GENERAL);
1657                props.append("size=").append(width).append(':').append(height).append(';');
1658    //            logger.trace("window props: {}", props);
1659                if ("MAP".equals(panelTypeRepr)) {
1660                    node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager");
1661                } else if ("GLOBE".equals(panelTypeRepr)) {
1662                    node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager");
1663                    props.append(DYNSKIN_PROPS_GLOBE);
1664                } else if ("TRANSECT".equals(panelTypeRepr)) {
1665                    node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.TransectViewManager");
1666                } else if ("MAP2D".equals(panelTypeRepr)) {
1667                    node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager");
1668                    props.append("use3D=false;");
1669                }
1670                view.setAttribute(DYNSKIN_ATTR_PROPS, props.toString());
1671                view.appendChild(node);
1672            }
1673            panel.appendChild(view);
1674            UIManager uiManager = (UIManager)McIDASV.getStaticMcv().getIdvUIManager();
1675            Element skinRoot = XmlUtil.getRoot(Constants.BLANK_COMP_GROUP, PersistenceManager.class);
1676            IdvWindow window = uiManager.createNewWindow(null, false, "McIDAS-V", Constants.BLANK_COMP_GROUP, skinRoot, false, null);
1677            ComponentGroup group = window.getComponentGroups().get(0);
1678            McvComponentHolder holder = new McvComponentHolder(McIDASV.getStaticMcv(), XmlUtil.toString(root));
1679            holder.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN);
1680            holder.setName(DYNSKIN_TMPNAME);
1681            group.addComponent(holder);
1682            return window;
1683        }
1684    
1685        private static final String DYNSKIN_TMPNAME = "McIDAS-V buildWindow";
1686        private static final String DYNSKIN_TAG_PANEL = "panel";
1687        private static final String DYNSKIN_TAG_VIEW = "idv.view";
1688        private static final String DYNSKIN_ATTR_ID = "id";
1689        private static final String DYNSKIN_ATTR_COLS = "cols";
1690        private static final String DYNSKIN_ATTR_ROWS = "rows";
1691        private static final String DYNSKIN_ATTR_PROPS = "properties";
1692        private static final String DYNSKIN_ATTR_CLASS = "class";
1693        private static final String DYNSKIN_ATTR_VIEWID = "viewid";
1694        private static final String DYNSKIN_PROPS_GLOBE = "useGlobeDisplay=true;initialMapResources=/edu/wisc/ssec/mcidasv/resources/maps.xml;";
1695        private static final String DYNSKIN_PROPS_GENERAL = "clickToFocus=true;showToolBars=true;shareViews=true;showControlLegend=true;initialSplitPaneLocation=0.2;legendOnLeft=false;showEarthNavPanel=false;showControlLegend=false;shareGroup=view%versionuid%;";
1696        private static final String DYNSKIN_ID_VALUE = "mcv.content";
1697    
1698        /** XML template for generating dynamic skins. */
1699        private static final String SIMPLE_SKIN_TEMPLATE = 
1700            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
1701            "<skin embedded=\"true\">\n" +
1702            "  <ui>\n" +
1703            "    <panel layout=\"border\" bgcolor=\"red\">\n" +
1704            "      <idv.menubar place=\"North\"/>\n" +
1705            "      <panel layout=\"border\" place=\"Center\">\n" +
1706            "        <panel layout=\"flow\" place=\"North\">\n" +
1707            "          <idv.toolbar id=\"idv.toolbar\" place=\"West\"/>\n" +
1708            "          <panel id=\"idv.favoritesbar\" place=\"North\"/>\n" +
1709            "        </panel>\n" +
1710            "        <panel embeddednode=\"true\" id=\"mcv.content\" layout=\"grid\" place=\"Center\">\n" +
1711            "        </panel>" +
1712            "      </panel>\n" +
1713            "      <component idref=\"bottom_bar\"/>\n" +
1714            "    </panel>\n" +
1715            "  </ui>\n" +
1716            "  <styles>\n" +
1717            "    <style class=\"iconbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip);ui.setBorder(this,etched);\" mouse_exit=\"ui.setText(idv.messagelabel,);ui.setBorder(this,button);\"/>\n" +
1718            "    <style class=\"textbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip)\" mouse_exit=\"ui.setText(idv.messagelabel,)\"/>\n" +
1719            "  </styles>\n" +
1720            "  <components>\n" +
1721            "    <idv.statusbar place=\"South\" id=\"bottom_bar\"/>\n" +
1722            "  </components>\n" +
1723            "  <properties>\n" +
1724            "    <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" +
1725            "  </properties>\n" +
1726            "</skin>\n";
1727        
1728        
1729        
1730        /**
1731         * Write the parameter sets
1732         */
1733        public void writeParameterSets() {
1734            if (parameterSets != null) {
1735                    
1736                    //DAVEP: why is our write failing?
1737                    if (!parameterSets.hasWritableResource()) {
1738                            System.err.println("Oops--lost writable resource");
1739                    }
1740                    
1741                    try {
1742                            parameterSets.writeWritable();
1743                    } catch (IOException exc) {
1744                    LogUtil.logException("Error writing " + parameterSets.getDescription(), exc);
1745                    }
1746                    
1747                    parameterSets.setWritableDocument(parameterSetsDocument, parameterSetsRoot);
1748            }
1749        }
1750        
1751        /**
1752         * Get the node representing the parameterType
1753         * 
1754         * @param parameterType What type of parameter set
1755         *
1756         * @return Element representing parameterType node
1757         */
1758        private Element getParameterTypeNode(String parameterType) {
1759            if (parameterSets == null) {
1760                    parameterSets = getIdv().getResourceManager().getXmlResources(ResourceManager.RSC_PARAMETERSETS);
1761                if (parameterSets.hasWritableResource()) {
1762                    parameterSetsDocument = parameterSets.getWritableDocument("<parametersets></parametersets>");
1763                    parameterSetsRoot = parameterSets.getWritableRoot("<parametersets></parametersets>");
1764                }
1765                else {
1766                    System.err.println("No writable resource found");
1767                    return null;
1768                }
1769            }
1770    
1771            Element parameterTypeNode = null;
1772            try {
1773                    List<Element> rootTypes = XmlUtil.findChildren(parameterSetsRoot, parameterType);
1774                    if (rootTypes.size() == 0) {
1775                            parameterTypeNode = parameterSetsDocument.createElement(parameterType);
1776                            parameterSetsRoot.appendChild(parameterTypeNode);
1777                            System.out.println("Created new " + parameterType + " node");
1778                            writeParameterSets();
1779                    }
1780                    else if (rootTypes.size() == 1) {
1781                            parameterTypeNode = rootTypes.get(0);
1782                            System.out.println("Found existing " + parameterType + " node");
1783                    }
1784            } catch (Exception exc) {
1785                    LogUtil.logException("Error loading " + parameterSets.getDescription(), exc);
1786            }
1787            return parameterTypeNode;
1788        }
1789    
1790        /**
1791         * Get a list of all of the categories for the given parameterType
1792         *
1793         * @param parameterType What type of parameter set
1794         *
1795         * @return List of (String) categories
1796         */
1797        public List<String> getAllParameterSetCategories(String parameterType) {
1798            List<String> allCategories = new ArrayList<String>();
1799            try {
1800                    Element rootType = getParameterTypeNode(parameterType);
1801                    if (rootType!=null) {
1802                            allCategories =
1803                                    XmlUtil.findDescendantNamesWithSeparator(rootType, TAG_FOLDER, CATEGORY_SEPARATOR);
1804                    }
1805            } catch (Exception exc) {
1806                    LogUtil.logException("Error loading " + parameterSets.getDescription(), exc);
1807            }
1808    
1809            return allCategories;
1810        }
1811        
1812    
1813        /**
1814         * Get the list of {@link ParameterSet}s that are writable
1815         *
1816         * @param parameterType The type of parameter set
1817         *
1818         * @return List of writable parameter sets
1819         */
1820        public List<ParameterSet> getAllParameterSets(String parameterType) {
1821            List<ParameterSet> allParameterSets = new ArrayList<ParameterSet>();
1822            try {
1823                    Element rootType = getParameterTypeNode(parameterType);
1824                    if (rootType!=null) {
1825                    List<String> defaults =
1826                            XmlUtil.findDescendantNamesWithSeparator(rootType, TAG_DEFAULT, CATEGORY_SEPARATOR);
1827                            
1828                            for (final String aDefault : defaults) {
1829                                    Element anElement = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(aDefault));
1830                                    List<String> defaultParts = stringToCategories(aDefault);
1831                                    int lastIndex = defaultParts.size() - 1;
1832                                    String defaultName = defaultParts.get(lastIndex);
1833                                    defaultParts.remove(lastIndex);
1834                                    String folderName = StringUtil.join(CATEGORY_SEPARATOR, defaultParts);
1835                                    ParameterSet newSet = new ParameterSet(defaultName, folderName, parameterType, anElement);
1836                                    allParameterSets.add(newSet);
1837                            }
1838    
1839                    }
1840            } catch (Exception exc) {
1841                    LogUtil.logException("Error loading " + ResourceManager.RSC_PARAMETERSETS.getDescription(), exc);
1842            }
1843            
1844            return allParameterSets;
1845        }
1846    
1847    
1848        /**
1849         * Add the directory
1850         *
1851         * @param parameterType The type of parameter set
1852         * @param category The category (really a ">" delimited string)
1853         * @return true if the create was successful. False if there already is a category with that name
1854         */
1855        public boolean addParameterSetCategory(String parameterType, String category) {
1856            System.out.println("addParameterSetCategory: " + category);
1857                    Element rootType = getParameterTypeNode(parameterType);
1858            XmlUtil.makeElementAtNamedPath(rootType, stringToCategories(category), TAG_FOLDER);
1859            writeParameterSets();
1860            return true;
1861        }
1862        
1863        /**
1864         * Delete the given parameter set
1865         *
1866         * @param parameterType The type of parameter set
1867         * @param set Parameter set to delete.
1868         */
1869        public void deleteParameterSet(String parameterType, ParameterSet set) {
1870            Element parameterElement = set.getElement();
1871            Node parentNode = parameterElement.getParentNode();
1872            parentNode.removeChild((Node)parameterElement);
1873            writeParameterSets();
1874        }
1875    
1876    
1877        /**
1878         * Delete the directory and all of its contents
1879         * that the given category represents.
1880         *
1881         * @param parameterType The type of parameter set
1882         * @param category The category (really a ">" delimited string)
1883         */
1884        public void deleteParameterSetCategory(String parameterType, String category) {
1885                    Element rootType = getParameterTypeNode(parameterType);
1886            Element parameterSetElement = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(category));
1887            Node parentNode = parameterSetElement.getParentNode();
1888            parentNode.removeChild((Node)parameterSetElement);
1889            writeParameterSets();
1890        }
1891    
1892    
1893        /**
1894         * Rename the parameter set
1895         *
1896         * @param parameterType The type of parameter set
1897         * @param set The parameter set
1898         */
1899        public void renameParameterSet(String parameterType, ParameterSet set) {
1900            String name = set.getName();
1901            Element parameterElement = set.getElement();
1902    //        while (true) {
1903                name = GuiUtils.getInput("Enter a new name", "Name: ", name);
1904                if (name == null) {
1905                    return;
1906                }
1907                            name = StringUtil.replaceList(name.trim(),
1908                                            new String[] { "<", ">", "/", "\\", "\"" },
1909                                            new String[] { "_", "_", "_", "_",  "_"  }
1910                            );
1911                if (name.length() == 0) {
1912                    return;
1913                }
1914    //        }
1915            parameterElement.setAttribute("name", name);
1916            writeParameterSets();
1917        }
1918        
1919        /**
1920         * Move the bundle to the given category area
1921         *
1922         * @param parameterType The type of parameter set
1923         * @param set The parameter set
1924         * @param categories Where to move to
1925         */
1926        public void moveParameterSet(String parameterType, ParameterSet set, List categories) {
1927                    Element rootType = getParameterTypeNode(parameterType);
1928            Element parameterElement = set.getElement();
1929            Node parentNode = parameterElement.getParentNode();
1930            parentNode.removeChild((Node)parameterElement);
1931            Node newParentNode = XmlUtil.getElementAtNamedPath(rootType, categories);
1932            newParentNode.appendChild(parameterElement);
1933            writeParameterSets();
1934        }
1935    
1936        /**
1937         * Move the bundle category
1938         *
1939         * @param parameterType The type of parameter set
1940         * @param fromCategories The category to move
1941         * @param toCategories Where to move to
1942         */
1943        public void moveParameterSetCategory(String parameterType, List fromCategories, List toCategories) {
1944                    Element rootType = getParameterTypeNode(parameterType);
1945            Element parameterSetElementFrom = XmlUtil.getElementAtNamedPath(rootType, fromCategories);
1946            Node parentNode = parameterSetElementFrom.getParentNode();
1947            parentNode.removeChild((Node)parameterSetElementFrom);
1948            Node parentNodeTo = (Node)XmlUtil.getElementAtNamedPath(rootType, toCategories);
1949            parentNodeTo.appendChild(parameterSetElementFrom);
1950            writeParameterSets();
1951        }
1952    
1953        /**
1954         * Show the Save Parameter Set dialog
1955         */
1956        public boolean saveParameterSet(String parameterType, Hashtable parameterValues) {
1957    
1958            try {
1959                    String title = "Save Parameter Set";
1960    
1961                    // Create the category dropdown
1962                    List<String> categories = getAllParameterSetCategories(parameterType);
1963                    final JComboBox catBox = new JComboBox();
1964                    catBox.setToolTipText(
1965                                    "<html>Categories can be entered manually. <br>Use '>' as the category delimiter. e.g.:<br>General > Subcategory</html>");
1966                    catBox.setEditable(true);
1967                    McVGuiUtils.setComponentWidth(catBox, McVGuiUtils.ELEMENT_DOUBLE_WIDTH);
1968                    GuiUtils.setListData(catBox, categories);
1969    
1970                    // Create the default name dropdown
1971                    final JComboBox nameBox = new JComboBox();
1972                    nameBox.setEditable(true);
1973                    List tails = new ArrayList();
1974    
1975                    List<ParameterSet> pSets = getAllParameterSets(parameterType);
1976                    for (int i = 0; i < pSets.size(); i++) {
1977                            ParameterSet pSet = pSets.get(i);
1978                            tails.add(new TwoFacedObject(pSet.getName(), pSet));
1979                    }
1980                    java.util.Collections.sort(tails);
1981    
1982                    tails.add(0, new TwoFacedObject("", null));
1983                    GuiUtils.setListData(nameBox, tails);
1984                    nameBox.addActionListener(new ActionListener() {
1985                            public void actionPerformed(ActionEvent ae) {
1986                                    Object selected = nameBox.getSelectedItem();
1987                                    if ( !(selected instanceof TwoFacedObject)) {
1988                                            return;
1989                                    }
1990                                    TwoFacedObject tfo = (TwoFacedObject) selected;
1991                                    List cats = ((ParameterSet) tfo.getId()).getCategories();
1992                                    //                      if ((cats.size() > 0) && !catSelected) {
1993                                    if ((cats.size() > 0)) {
1994                                            catBox.setSelectedItem(
1995                                                            StringUtil.join(CATEGORY_SEPARATOR, cats));
1996                                    }
1997                            }
1998                    });
1999    
2000                    JPanel panel = McVGuiUtils.sideBySide(
2001                                    McVGuiUtils.makeLabeledComponent("Category:", catBox),
2002                                    McVGuiUtils.makeLabeledComponent("Name:", nameBox)
2003                    );
2004    
2005                    String name = "";
2006                    String category = "";
2007                    while (true) {
2008                            if ( !GuiUtils.askOkCancel(title, panel)) {
2009                                    return false;
2010                            }
2011                            name = StringUtil.replaceList(nameBox.getSelectedItem().toString().trim(),
2012                                            new String[] { "<", ">", "/", "\\", "\"" },
2013                                            new String[] { "_", "_", "_", "_",  "_"  }
2014                            );
2015                            if (name.length() == 0) {
2016                                    LogUtil.userMessage("Please enter a name");
2017                                    continue;
2018                            }
2019                            category = StringUtil.replaceList(catBox.getSelectedItem().toString().trim(),
2020                                            new String[] { "/", "\\", "\"" },
2021                                            new String[] { "_", "_",  "_"  }
2022                            );
2023                            if (category.length() == 0) {
2024                                    LogUtil.userMessage("Please enter a category");
2025                                    continue;
2026                            }
2027                            break;
2028                    }
2029    
2030                    // Create a new element from the hashtable
2031                    Element rootType = getParameterTypeNode(parameterType);
2032                    Element parameterElement = parameterSetsDocument.createElement(TAG_DEFAULT);
2033                for (Enumeration e = parameterValues.keys(); e.hasMoreElements(); ) {
2034                    Object nextKey = e.nextElement();
2035                    String attribute = (String)nextKey;
2036                    String value = (String)parameterValues.get(nextKey);
2037                    parameterElement.setAttribute(attribute, value);
2038                }
2039    
2040                    // Set the name to the one we entered
2041                    parameterElement.setAttribute(ATTR_NAME, name);
2042    
2043                    Element categoryNode = XmlUtil.makeElementAtNamedPath(rootType, stringToCategories(category), TAG_FOLDER);
2044    //              Element categoryNode = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(category));
2045    
2046                    categoryNode.appendChild(parameterElement);     
2047                    writeParameterSets();
2048            }
2049            catch (Exception e) {
2050                    e.printStackTrace();
2051                    return false;
2052            }
2053            
2054            return true;
2055        }
2056    
2057    }