001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    
029    package edu.wisc.ssec.mcidasv;
030    
031    import java.rmi.RemoteException;
032    import java.util.ArrayList;
033    import java.util.Collections;
034    import java.util.List;
035    
036    import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
037    import ucar.unidata.idv.ArgsManager;
038    import ucar.unidata.idv.IntegratedDataViewer;
039    import ucar.unidata.util.IOUtil;
040    import ucar.unidata.util.LogUtil;
041    import ucar.unidata.util.PatternFileFilter;
042    import visad.VisADException;
043    
044    /**
045     * McIDAS-V needs to handle a few command line flags/options that the IDV does
046     * not. Only the ability to force the Aqua look and feel currently exists.
047     * 
048     * @author McIDAS-V Developers
049     */
050    public class ArgumentManager extends ArgsManager {
051    
052        /** McIDAS-V flag that signifies everything that follows is a Jython argument. */
053        public static final String ARG_JYTHONARGS = "-scriptargs";
054    
055        /** usage message */
056        public static final String USAGE_MESSAGE =
057            "Usage: runMcV [OPTIONS] <bundle/script files, e.g., .mcv, .mcvz, .py>";
058    
059        /** Jython arguments, if any. */
060        private List<String> jythonArguments;
061        
062        /** Jython script to execute, or {@literal "<none>"} if one was not given. */
063        private String jythonScript;
064    
065        /**
066         *  Given by the "-user" argument. Alternative user path for bundles,  resources, etc.
067         */
068        String defaultUserDirectory = StartupManager.getInstance().getPlatform().getUserDirectory();
069    
070        /**
071         * Just bubblin' on up the inheritance hierarchy.
072         * 
073         * @param idv The IDV instance.
074         * @param args The command line arguments that were given.
075         */
076        public ArgumentManager(IntegratedDataViewer idv, String[] args) {
077            super(idv, args);
078            jythonArguments = new ArrayList<String>();
079            jythonScript = "<none>";
080        }
081    
082        private static List<String> extractJythonArgs(int index, String[] args) {
083            List<String> jythonArgs = new ArrayList<String>(args.length);
084            for (int i = index; i < args.length; i++) {
085                jythonArgs.add(args[i]);
086            }
087            return jythonArgs;
088        }
089    
090        /**
091         * Currently we're only handling the {@code -forceaqua} flag so we can
092         * mitigate some overlay issues we've been seeing on OS X Leopard.
093         * 
094         * @param arg The current argument we're examining.
095         * @param args The actual array of arguments.
096         * @param idx The index of {@code arg} within {@code args}.
097         * 
098         * @return The idx of the last value in the args array we look at. i.e., 
099         * if the flag arg does not require any further values in the args array 
100         * then don't increment idx.  If arg requires one more value then 
101         * increment idx by one. etc.
102         * 
103         * @throws Exception Throw bad things off to something that can handle 'em!
104         */
105        protected int parseArg(String arg, String[] args, int idx) 
106            throws Exception {
107    
108            if ("-forceaqua".equals(arg)) {
109                // unfortunately we can't simply set the look and feel here. If I
110                // were to do so, the loadLookAndFeel in the IdvUIManager would 
111                // eventually get loaded and then set the look and feel to whatever
112                // the preferences dictate.
113                // instead I use the boolean toggle to signal to McV's 
114                // UIManager.loadLookAndFeel that it should simply ignore the user's
115                // preference is and load the Aqua L&F from there.
116                McIDASV.useAquaLookAndFeel = true;
117            } else if (ARG_HELP.equals(arg)) {
118                System.err.println(USAGE_MESSAGE);
119                System.err.println(getUsageMessage());
120                ((McIDASV)getIdv()).exit(1);
121            } else if (checkArg(arg, "-script", args, idx, 1) || checkArg(arg, "-pyfile", args, idx, 1)) {
122                String scriptArg = args[idx++];
123                jythonScript = scriptArg;
124                scriptingFiles.add(scriptArg);
125                if (!getIslInteractive()) {
126                    setIsOffScreen(true);
127                }
128            } else if ("-console".equals(arg)) {
129                System.err.println("*** WARNING: console flag is likely to go away soon!");
130            } else if (ARG_JYTHONARGS.equals(arg)) {
131                if (scriptingFiles.isEmpty()) {
132                    System.err.println("*** WARNING: Jython script arguments will be ignored unless you provide a Jython script to execute!");
133                } else {
134                    jythonArguments.addAll(extractJythonArgs(idx, args));
135                    
136                    // jump to end of args to halt further idv processing.
137                    return args.length;
138                }
139            } else {
140                if (ARG_ISLINTERACTIVE.equals(arg) || ARG_B64ISL.equals(arg) || ARG_ISLFILE.equals(arg) || isIslFile(arg)) {
141                    System.err.println("*** WARNING: ISL is being deprecated!");
142                }
143                return super.parseArg(arg, args, idx);
144            }
145            return idx;
146        }
147    
148        /**
149         * Print out the command line usage message and exit
150         * 
151         * @param err The usage message
152         */
153        @Override public void usage(String err) {
154            String msg = USAGE_MESSAGE;
155            msg = msg + '\n' + getUsageMessage();
156            LogUtil.userErrorMessage(err + '\n' + msg);
157            ((McIDASV)getIdv()).exit(1);
158        }
159    
160        /**
161         * Append some McIDAS-V specific command line options to the default IDV
162         * usage message.
163         *
164         * @return Usage message.
165         */
166        protected String getUsageMessage() {
167            return msg(ARG_HELP, "(this message)")
168                + msg("-forceaqua", "Forces the Aqua look and feel on OS X")
169                + msg(ARG_PROPERTIES, "<property file>")
170                + msg("-Dpropertyname=value", "(Define the property value)")
171                + msg(ARG_INSTALLPLUGIN, "<plugin jar file or url to install>")
172                + msg(ARG_PLUGIN, "<plugin jar file, directory, url for this run>")
173                + msg(ARG_NOPLUGINS, "Don't load plugins")
174                + msg(ARG_CLEARDEFAULT, "(Clear the default bundle)")
175                + msg(ARG_NODEFAULT, "(Don't read in the default bundle file)")
176                + msg(ARG_DEFAULT, "<.mcv/.mcvz file>")
177                + msg(ARG_BUNDLE, "<bundle file or url>")
178                + msg(ARG_B64BUNDLE, "<base 64 encoded inline bundle>")
179                + msg(ARG_SETFILES, "<datasource pattern> <semi-colon delimited list of files> (Use the list of files for the bundled datasource)")
180                + msg(ARG_ONEINSTANCEPORT, "<port number> (Check if another version of McIDAS-V is running. If so pass command line arguments to it and shutdown)")
181                + msg(ARG_NOONEINSTANCE, "(Don't do the one instance port)")
182                + msg(ARG_NOPREF, "(Don't read in the user preferences)")
183                + msg(ARG_USERPATH, "<user directory to use>")
184                + msg(ARG_SITEPATH, "<url path to find site resources>")
185                + msg(ARG_NOGUI, "(Don't show the main window gui)")
186                + msg(ARG_DATA, "<data source> (Load the data source)")
187                + msg(ARG_DISPLAY, "<parameter> <display>")
188    //            + msg("<scriptfile.isl>", "(Run the IDV script in batch mode)")
189                + msg("-script", "<jython script file to evaluate>")
190                + msg("-pyfile", "<jython script file to evaluate>")
191                + msg(ARG_JYTHONARGS, "All arguments after this flag will be considered Jython arguments.")
192    //            + msg(ARG_B64ISL, "<base64 encoded inline isl> This will run the isl in interactive mode")
193    //            + msg(ARG_ISLINTERACTIVE, "run any isl files in interactive mode")
194                + msg(ARG_IMAGE, "<image file name> (create a jpeg image and then exit)")
195                + msg(ARG_MOVIE, "<movie file name> (create a quicktime movie and then exit)")
196                + msg(ARG_IMAGESERVER, "<port number or .properties file> (run McIDAS-V in image generation server mode. Support http requests on the given port)")
197                + msg(ARG_CATALOG, "<url to a chooser catalog>")
198                + msg(ARG_CONNECT, "<collaboration hostname to connect to>")
199                + msg(ARG_SERVER, "(Should McIDAS-V run in collaboration server mode)")
200                + msg(ARG_PORT, "<Port number collaboration server should listen on>")
201                + msg(ARG_CHOOSER, "(show the data chooser on start up) ")
202                + msg(ARG_PRINTJNLP, "(Print out any embedded bundles from jnlp files)")
203                + msg(ARG_CURRENTTIME, "<dttm> (Override current time for background processing)")
204    //            + msg(ARG_CURRENTTIME, "<dttm> (Override current time for ISL processing)")
205                + msg(ARG_LISTRESOURCES, "<list out the resource types")
206                + msg(ARG_DEBUG, "(Turn on debug print)")
207                + msg(ARG_MSG_DEBUG, "(Turn on language pack debug)")
208                + msg(ARG_MSG_RECORD, "<Language pack file to write missing entries to>")
209                + msg(ARG_TRACE, "(Print out trace messages)")
210                + msg(ARG_NOERRORSINGUI, "(Don't show errors in gui)")
211                + msg(ARG_TRACEONLY, "<trace pattern> (Print out trace messages that match the pattern)")
212                + msg("-console", "[ fix for getting the console functionality in install4j launcher ]");
213        }
214        
215        /**
216         * Determine whether or not the user has provided any arguments for a 
217         * Jython script.
218         * 
219         * @return {@code true} if the user has provided Jython arguments, 
220         * {@code false} otherwise.
221         */
222        public boolean hasJythonArguments() {
223            return !jythonArguments.isEmpty();
224        }
225        
226        /**
227         * Returns Jython arguments. <b>Note:</b> this does not include the Jython
228         * script that will be executed.
229         * 
230         * @return Either a {@link List} of {@link String Strings} containing the
231         * arguments or an empty {@code List} if there were no arguments given.
232         */
233        public List<String> getJythonArguments() {
234            return jythonArguments;
235        }
236        
237        /**
238         * Returns the name of the Jython script the user has provided.
239         * 
240         * @return Either the path to a Jython file or {@literal "<none>"} if the
241         * user did not provide a script.
242         */
243        public String getJythonScript() {
244            return jythonScript;
245        }
246        
247        /**
248         * Gets called by the IDV to process the set of initial files, e.g.,
249         * default bundles, command line bundles, jnlp files, etc.
250         * 
251         * <p>Overridden by McIDAS-V to remove bundle file paths that are zero
252         * characters long. This was happening because {@code runMcV.bat} was
253         * always passing {@literal '-bundle ""'} on the command line (for Windows). 
254         * 
255         * @throws VisADException When something untoward happens
256         * @throws RemoteException When something untoward happens
257         */
258        @Override protected void processInitialBundles()
259                throws VisADException, RemoteException 
260        {
261            for (int i = 0; i < argXidvFiles.size(); i++) {
262                String path = (String)argXidvFiles.get(i);
263                if (path.isEmpty()) {
264                    argXidvFiles.remove(i);
265                }
266            }
267            super.processInitialBundles();
268        }
269        
270        /**
271         * @see ArgsManager#getBundleFileFilters()
272         */
273        @Override public List<PatternFileFilter> getBundleFileFilters() {
274            List<PatternFileFilter> filters = new ArrayList<PatternFileFilter>(); 
275            Collections.addAll(filters, getXidvFileFilter(), getZidvFileFilter(), FILTER_JNLP, FILTER_ISL, super.getXidvFileFilter(), super.getZidvFileFilter());
276            return filters;
277        }
278    
279        /**
280         * Returns a list of {@link PatternFileFilter}s that can be used to determine
281         * if a file is a bundle. 
282         * 
283         * <p>If {@code fromOpen} is {@code true}, the 
284         * returned list will contain {@code PatternFileFilter}s for bundles as 
285         * well as JNLP and ISL files. If {@code false}, the returned list will
286         * only contain filters for XML and zipped bundles.
287         * 
288         * @param fromOpen Whether or not this has been called from an 
289         * {@literal "open file"} dialog. 
290         * 
291         * @return Filters for bundles.
292         */
293        public List<PatternFileFilter> getBundleFilters(final boolean fromOpen) {
294            List<PatternFileFilter> filters = new ArrayList<PatternFileFilter>();
295    
296            if (fromOpen)
297                Collections.addAll(filters, getXidvZidvFileFilter(), FILTER_JNLP, FILTER_ISL, super.getXidvZidvFileFilter());
298            else
299                filters.addAll(getBundleFileFilters());
300    
301            return filters;
302        }
303    
304        /**
305         * @see ArgsManager#getXidvFileFilter()
306         */
307        @Override public PatternFileFilter getXidvFileFilter() {
308            return Constants.FILTER_MCV;
309        }
310    
311        /**
312         * @see ArgsManager#getZidvFileFilter()
313         */
314        @Override public PatternFileFilter getZidvFileFilter() {
315            return Constants.FILTER_MCVZ;
316        }
317    
318        /**
319         * @see ArgsManager#getXidvZidvFileFilter()
320         */
321        @Override public PatternFileFilter getXidvZidvFileFilter() {
322            return Constants.FILTER_MCVMCVZ;
323        }
324    
325        /*
326         * There's some internal IDV file opening code that relies on this method.
327         * We've gotta override if we want to use .zidv bundles.
328         */
329        @Override public boolean isZidvFile(final String name) {
330            return isZippedBundle(name);
331        }
332    
333        /* same story as isZidvFile! */
334        @Override public boolean isXidvFile(final String name) {
335            return isXmlBundle(name);
336        }
337    
338        /**
339         * Tests to see if <code>name</code> has a known XML bundle extension.
340         * 
341         * @param name Name of the bundle.
342         * 
343         * @return Whether or not <code>name</code> has an XML bundle suffix.
344         */
345        public static boolean isXmlBundle(final String name) {
346            return IOUtil.hasSuffix(name, Constants.FILTER_MCV.getPreferredSuffix())
347                || IOUtil.hasSuffix(name, Constants.FILTER_XIDV.getPreferredSuffix());
348        }
349    
350        /**
351         * Tests to see if <code>name</code> has a known zipped bundle extension.
352         * 
353         * @param name Name of the bundle.
354         * 
355         * @return Whether or not <code>name</code> has zipped bundle suffix.
356         */
357        public static boolean isZippedBundle(final String name) {
358            return IOUtil.hasSuffix(name, Constants.FILTER_MCVZ.getPreferredSuffix())
359                   || IOUtil.hasSuffix(name, Constants.FILTER_ZIDV.getPreferredSuffix());
360        }
361    
362        /**
363         * Tests <code>name</code> to see if it has a known bundle extension.
364         * 
365         * @param name Name of the bundle.
366         * 
367         * @return Whether or not <code>name</code> has a bundle suffix.
368         */
369        public static boolean isBundle(final String name) {
370            return (isXmlBundle(name) || isZippedBundle(name));
371        }
372    }