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