001    package org.LiveGraph.bootstrap;
002    
003    import java.io.File;
004    import java.util.ArrayList;
005    import java.util.List;
006    
007    import org.LiveGraph.settings.DataFileSettings;
008    import org.LiveGraph.settings.DataSeriesSettings;
009    import org.LiveGraph.settings.GraphSettings;
010    
011    /**
012     * This class represents a parser for command line arguments for a LiveGraph application.
013     * 
014     * <p>
015     *   <strong>LiveGraph</strong>
016     *   (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>).
017     * </p> 
018     * <p>Copyright (c) 2007-2008 by G. Paperin.</p>
019     * <p>File: CommandLineProcessor.java</p>
020     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
021     *    without modification, are permitted provided that the following terms and conditions are met:
022     * </p>
023     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
024     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
025     *    this list of conditions and the following disclaimer.<br />
026     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
027     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
028     *    and the following disclaimer in the documentation and/or other materials provided with
029     *    the distribution.<br />
030     *    3. All advertising materials mentioning features or use of this software or any derived
031     *    software must display the following acknowledgement:<br />
032     *    <em>This product includes software developed by the LiveGraph project and its
033     *    contributors.<br />(http://www.live-graph.org)</em><br />
034     *    4. All advertising materials distributed in form of HTML pages or any other technology
035     *    permitting active hyper-links that mention features or use of this software or any
036     *    derived software must display the acknowledgment specified in condition 3 of this
037     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
038     *    homepage (http://www.live-graph.org).
039     * </p>
040     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
041     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
042     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
043     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
044     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
045     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
046     * </p>
047     * 
048     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
049     * @version {@value org.LiveGraph.LiveGraph#version}
050     *
051     */
052    public class CommandLineProcessor {
053    
054    /**
055     * System specific new-line string.
056     */
057    public static final String newLine = String.format("%n");
058    
059    /**
060     * Message specifying the correct usage of command line argumnts.
061     */
062    public static final String correctPromptMsg =
063                            "Legal command line arguments are as follows: " + newLine
064                      + "    > java edu.monash.LiveGraph.LiveGraph [-dfs \"{data file settings file}\"] " + newLine
065                      + "                                          [-gs \"{graph settings file}\"] " + newLine
066                      + "                                          [-dss \"{data series settings file}\"] " + newLine
067                      + "                                          [-f \"{actual data file}\"] " + newLine
068                      + "    This means the program expects either 0, 2, 4, 6 or 8 command line arguments. " + newLine
069                      + "    Note: '-f' overrides any data file that may be specified within " + newLine
070                      + "          a data file settings file that may be set with '-dfs'.";
071    
072    
073    /**
074     * Holds any error messages occured during parsing.
075     */
076    private List<String> errMessages = new ArrayList<String>();
077    
078    /**
079     * Flag set when any errors during argument parsing or validation occur.
080     */
081    private boolean hasError = false;
082    
083    
084    /**
085     * Command line arguments to parse.
086     */
087    private String[] args = null;
088    
089    /**
090     * The data file settings file if such was parsed.
091     */
092    private File file_DataFileSettings = null;
093    
094    /**
095     * The graph settings file if such was parsed.
096     */
097    private File file_GraphSettings = null;
098    
099    /**
100     * The data series settings file if such was parsed.
101     */
102    private File file_DataSeriesSettings = null;
103    
104    /**
105     * The data file if such was parsed.
106     */
107    private File file_Data = null;
108    
109    
110    /**
111     * Initialises the parser for an empty command line.
112     *
113     */
114    public CommandLineProcessor() {
115            this.args = new String[0];
116            processArgs();
117    }
118    
119    
120    /**
121     * Initialises the parser for the specified command line arguments.
122     * 
123     * @param args Command line arguments.
124     */
125    public CommandLineProcessor(String[] args) {
126            if (null == args) {
127                    this.args = new String[0];
128            } else {
129                    this.args = new String[args.length];
130                    for (int i = 0; i < args.length; i++)
131                            this.args[i] = args[i];
132            }
133            processArgs();
134    }
135    
136    
137    /**
138     * Used internally to process the arguments.
139     * First, parses the command line.
140     * Then, tries to apply default settings for arguments not specified on the command line.
141     */
142    private void processArgs() {
143            parseArgs();
144            supplementWithDefaults();
145    }
146    
147    
148    /**
149     * If data file, graph or data series settings are not set, checks whether the
150     * apropriate session.lgdfs, session.lggs and session.lgdss files are available
151     * and, if so, sets the argument values appropriately. 
152     */
153    private void supplementWithDefaults() {
154            
155            if (null == getFile_DataFileSettings()) {
156                    File f = new File("session" + DataFileSettings.preferredFileExtension);
157                    try {
158                            if (f.exists())
159                                    setFile_DataFileSettings(f.getAbsoluteFile());
160                    } catch(SecurityException e) {}
161            }
162            
163            if (null == getFile_GraphSettings()) {
164                    try {
165                            File f = new File("session" + GraphSettings.preferredFileExtension); 
166                            if (f.exists())
167                                    setFile_GraphSettings(f.getAbsoluteFile());
168                    } catch(SecurityException e) {}
169            }
170            
171            if (null == getFile_DataSeriesSettings()) {
172                    try {
173                            File f = new File("session" + DataSeriesSettings.preferredFileExtension); 
174                            if (f.exists())
175                                    setFile_DataSeriesSettings(f.getAbsoluteFile());
176                    } catch(SecurityException e) {}
177            }
178            
179            // No default for data file!
180    }
181    
182    
183    /**
184     * Parses the command line arguments for legal settings values.
185     */
186    private void parseArgs() {
187            
188            int parseIndex = 0;
189            while (parseIndex < args.length) {
190                    
191                    String arg = args[parseIndex].trim();
192                    
193                    if ("-dfs".equalsIgnoreCase(arg)) {
194                            File f = parseArgFile2(parseIndex, "file for data file settings", getFile_DataFileSettings());
195                            if (null != f)
196                                    setFile_DataFileSettings(f.getAbsoluteFile());
197                            parseIndex += 2;
198                            continue;
199                    }
200                    
201                    if ("-gs".equalsIgnoreCase(arg)) {
202                            File f = parseArgFile2(parseIndex, "file for graph settings", getFile_GraphSettings());
203                            if (null != f)
204                                    setFile_GraphSettings(f.getAbsoluteFile());
205                            parseIndex += 2;
206                            continue;
207                    }
208                    
209                    if ("-dss".equalsIgnoreCase(arg)) {
210                            File f = parseArgFile2(parseIndex, "file for data series settings", getFile_DataSeriesSettings());
211                            if (null != f)
212                                    setFile_DataSeriesSettings(f.getAbsoluteFile());
213                            parseIndex += 2;
214                            continue;
215                    }
216                    
217                    if ("-f".equalsIgnoreCase(arg)) {
218                            File f = parseArgFile2(parseIndex, "data file", getFile_Data());
219                            if (null != f)
220                                    setFile_Data(f.getAbsoluteFile());
221                            parseIndex += 2;
222                            continue;
223                    }
224                    
225                    error(parseIndex, arg, "This is an illegal argument and cannot be processed. It will be ignored.");
226                    parseIndex += 1;
227            }
228    }
229    
230    
231    /**
232     * Validates a pair of command line arguments,
233     * where the first is a flag and the second is a file path.
234     *  
235     * @param parseIndex The argument index of the flag, i.e. the first argument of the pair. 
236     * @param argDesc A textual description of the current flag (for megsages).
237     * @param currValue The current value for the setting corresponding to the flag.
238     * 
239     * @return A validated File object that stands for the path described by the second argument.
240     */
241    private File parseArgFile2(int parseIndex, String argDesc, File currValue) {
242            
243            if (parseIndex + 1 >= args.length) {
244                    error(parseIndex, args[parseIndex],
245                              "This argument specifies '" + argDesc + "' and must be followed by a file path.",
246                              "However, this argument is not followed by anything. It will be ignored.");
247                    return null;
248            }
249            
250            String fileName = args[parseIndex + 1].trim();
251            
252            if (null != currValue) {
253                    error(parseIndex, args[parseIndex],
254                              "This argument specifies '" + argDesc + "' and must be unique.",
255                              "However, '" + argDesc + "' is already set to '" + currValue + "'.",
256                              "The repeated argument will be ignored.");
257                    return null;
258            }
259            
260            File file = new File(fileName);
261            
262            try {
263                    if (!file.exists()) {
264                            error(parseIndex, args[parseIndex],
265                                      "This argument specifies '" + argDesc + "'.",
266                                      "However, the specified path '" + fileName + "' does not exist.",
267                                      "This argument will be ignored.");
268                            return null;
269                    }
270                    
271                    if (!file.canRead()) {
272                            error(parseIndex, args[parseIndex],
273                                      "This argument specifies '" + argDesc + "'.",
274                                      "However, the specified path '" + fileName + "' cannot be read.",
275                                      "This argument will be ignored.");
276                            return null;
277                    }
278                    
279                    if (file.isDirectory()) {
280                            error(parseIndex, args[parseIndex],
281                                      "This argument specifies '" + argDesc + "'.",
282                                      "However, the specified path '" + fileName + "' is a directory.",
283                                      "This argument will be ignored.");
284                            return null;
285                    }
286                    
287                    if (!file.isFile()) {
288                            error(parseIndex, args[parseIndex],
289                                      "This argument specifies '" + argDesc + "'.",
290                                      "However, the specified path '" + fileName + "' is not a normal file.",
291                                      "This argument will be ignored.");
292                            return null;
293                    }
294            } catch(SecurityException e) {
295                    error(parseIndex, args[parseIndex],
296                              "This argument specifies '" + argDesc + "'.",
297                              "However, the specified path '" + fileName + "' cannot be accessed by LiveGraph.",
298                              "Info: " + e.getMessage() + ".",
299                              "This argument will be ignored.");
300                    return null;
301            }
302            
303            return file;
304    }
305    
306    
307    /**
308     * Logs an error message.
309     * 
310     * @param argIndex Index of the argument to which the error relates. 
311     * @param argStr The argument to which the error relates.
312     * @param messages Error messages.
313     */
314    private void error(int argIndex, String argStr, String... messages) {
315            
316            StringBuffer s = new StringBuffer();
317            
318            s.append("Error in command line argument #");
319            s.append(argIndex + 1);
320            s.append(" (\"");
321            s.append(argStr);
322            s.append("\")");
323            s.append(newLine);
324            for (String msg : messages) {
325                    s.append("    ");
326                    s.append(msg);
327                    s.append(newLine);
328            }
329            
330            errMessages.add(s.toString());
331            hasError = true;
332    }
333    
334    
335    /**
336     * Checks whether any errors have occured.
337     * 
338     * @return {@code true} if at least one error occured during parsing or validation,
339     * {@code false} otherwise.
340     */
341    public boolean hasErrors() {
342            return hasError;
343    }
344    
345    
346    /**
347     * Constructs a {@code String} from all error messages that occured during parsing or validation.
348     * 
349     * @return A verbose message resarding all errors that occured during parsing or validation,
350     * or {@code null} if no errors have occured.
351     */
352    public String getErrorMessages() {
353            
354            if (!hasErrors())
355                    return null;
356            
357            StringBuffer s = new StringBuffer();
358            for (String msg : errMessages) {
359                    s.append(msg.trim());
360                    s.append(newLine);
361            }
362            s.append(correctPromptMsg.trim());
363            s.append(newLine);
364            
365            return s.toString();
366    }
367    
368    
369    /**
370     * File for data file settings - either parsed from the command line or the default session file. 
371     * @return Validated {@code File} from which LiveGraph should load the data file settings
372     * or {@code null} if no file was set or if the file is not present or cannot be accessed.
373     */
374    public File getFile_DataFileSettings() {
375            return file_DataFileSettings;
376    }
377    
378    
379    /**
380     * Sets the file for data file settings - either parsed from the command line or the default session file.
381     * @param f Validated {@code File} from which LiveGraph should load the data file settings.
382     */
383    private void setFile_DataFileSettings(File f) {
384            file_DataFileSettings = f;
385    }
386    
387    
388    /**
389     * File for graph settings - either parsed from the command line or the default session file. 
390     * @return Validated {@code File} from which LiveGraph should load the graph settings
391     * or {@code null} if no file was set or if the file is not present or cannot be accessed.
392     */
393    public File getFile_GraphSettings() {
394            return file_GraphSettings;
395    }
396    
397    
398    /**
399     * Sets the file for graph settings - either parsed from the command line or the default session file.
400     * @param f Validated {@code File} from which LiveGraph should load the graph settings.
401     */
402    private void setFile_GraphSettings(File f) {
403            file_GraphSettings = f;
404    }
405    
406    
407    /**
408     * File for data series settings - either parsed from the command line or the default session file. 
409     * @return Validated {@code File} from which LiveGraph should load the data series settings
410     * or {@code null} if no file was set or if the file is not present or cannot be accessed.
411     */
412    public File getFile_DataSeriesSettings() {
413            return file_DataSeriesSettings;
414    }
415    
416    
417    /**
418     * Sets the file for data series settings - either parsed from the command line or the default session file.
419     * @param f Validated {@code File} from which LiveGraph should load the data series settings.
420     */
421    private void setFile_DataSeriesSettings(File f) {
422            file_DataSeriesSettings = f;
423    }
424    
425    
426    /**
427     * Data file - parsed from the command line. 
428     * @return Validated {@code File} from which LiveGraph should load the data
429     * or {@code null} if no file was set or if the file is not present or cannot be accessed.
430     */
431    public File getFile_Data() {
432            return file_Data;
433    }
434    
435    
436    /**
437     * Sets the data file -parsed from the command line.
438     * @param f Validated {@code File} from which LiveGraph should load the data.
439     */
440    private void setFile_Data(File f) {
441            file_Data = f;
442    }
443    
444    
445    } // class CommandLineProcessor