View Javadoc

1   //Copyright 2006-2007, by the California Institute of Technology.
2   //ALL RIGHTS RESERVED. United States Government Sponsorship acknowledged.
3   //Any commercial use must be negotiated with the Office of Technology Transfer
4   //at the California Institute of Technology.
5   //
6   //This software is subject to U. S. export control laws and regulations
7   //(22 C.F.R. 120-130 and 15 C.F.R. 730-774). To the extent that the software
8   //is subject to U.S. export control laws and regulations, the recipient has
9   //the responsibility to obtain export licenses or other export authority as
10  //may be required before exporting such information to foreign countries or
11  //providing access to foreign nationals.
12  
13  // $Id: VTool.java 3392 2008-07-23 18:09:38Z mcayanan $
14  //
15  
16  package gov.nasa.pds.vtool;
17  
18  
19  import gov.nasa.pds.tools.dict.Dictionary;
20  import gov.nasa.pds.tools.dict.parser.DictionaryParser;
21  import gov.nasa.pds.tools.file.FileList;
22  import gov.nasa.pds.tools.file.FileListGenerator;
23  import gov.nasa.pds.tools.handler.ToolsFileHandler;
24  import gov.nasa.pds.tools.handler.ToolsStreamHandler;
25  import gov.nasa.pds.tools.label.Label;
26  import gov.nasa.pds.tools.label.parser.LabelParser;
27  import gov.nasa.pds.tools.label.parser.LabelParserFactory;
28  import gov.nasa.pds.tools.label.validate.Status;
29  import gov.nasa.pds.tools.license.ToolsLicense;
30  import gov.nasa.pds.tools.logging.ToolsLevel;
31  import gov.nasa.pds.tools.logging.ToolsLogFormatter;
32  import gov.nasa.pds.tools.logging.ToolsLogRecord;
33  import gov.nasa.pds.tools.options.ToolsOption;
34  import gov.nasa.pds.tools.report.StyleSheet;
35  import gov.nasa.pds.tools.time.ToolsTime;
36  import gov.nasa.pds.tools.util.Utility;
37  import gov.nasa.pds.vtool.config.VToolConfigKeys;
38  import gov.nasa.pds.vtool.flags.VToolFlags;
39  import gov.nasa.pds.vtool.handler.HandlerFactory;
40  import gov.nasa.pds.vtool.handler.UnknownHandlerConfigurationException;
41  import gov.nasa.pds.vtool.status.ExitStatus;
42  import gov.nasa.pds.vtool.status.ExitStatusType;
43  import gov.nasa.pds.vtool.validate.UnknownLabelStatusException;
44  import gov.nasa.pds.vtool.validate.ValidationRecord;
45  import gov.nasa.pds.vtool.validate.Validator;
46  import gov.nasa.pds.vtool.validate.ValidatorException;
47  import gov.nasa.pds.vtool.validate.ValidatorFactory;
48  import java.io.File;
49  import java.io.IOException;
50  import java.io.InputStream;
51  import java.net.MalformedURLException;
52  import java.net.URL;
53  import java.text.SimpleDateFormat;
54  import java.util.ArrayList;
55  import java.util.Arrays;
56  import java.util.Iterator;
57  import java.util.List;
58  import java.util.Properties;
59  import java.util.logging.Handler;
60  import java.util.logging.Level;
61  import java.util.logging.Logger;
62  
63  import org.apache.commons.cli.CommandLine;
64  import org.apache.commons.cli.CommandLineParser;
65  import org.apache.commons.cli.GnuParser;
66  import org.apache.commons.cli.HelpFormatter;
67  import org.apache.commons.cli.Options;
68  import org.apache.commons.cli.ParseException;
69  import org.apache.commons.configuration.AbstractConfiguration;
70  import org.apache.commons.configuration.Configuration;
71  import org.apache.commons.configuration.ConfigurationException;
72  import org.apache.commons.configuration.ConversionException;
73  import org.apache.commons.configuration.PropertiesConfiguration;
74  
75  /***
76   * Class to perform automated validation to determine if a given data product
77   * is PDS compliant.
78   * <p>
79   * This replaces LVTool functionality.
80   *  
81   * @author mcayanan
82   * @version $Revision: 3392 $
83   *
84   *
85   */
86  public class VTool implements VToolConfigKeys, VToolFlags, Status, 
87                                         ExitStatusType, ToolsLicense, StyleSheet {
88  	private final String FILE_REP = "*";
89  	private static Logger log = Logger.getLogger(VTool.class.getName());
90  	private String currDir;
91  	private Options options;
92  	//TODO: Flags to be implemented: data object(-O,--no-obj),
93  	private boolean alias;
94  	private URL config;
95  //	private boolean dataObj;
96  	private List dictionaries;
97  	private List userSpecifiedDictionaries;
98  	private List noFiles;
99  	private List noDirs;
100 	private boolean followPtrs;
101 	private List includePaths;
102 	private boolean force;
103 	private boolean progress;
104 	private List regexp;
105 	private boolean recursive;
106 	private List targets;
107 	private File logFile;
108 	private boolean showLog;
109 	private File rptFile;
110 	private String rptStyle;
111 	private short verbose;
112 	private String severity;
113 	private boolean dictionaryPassed;
114 	
115 	private final String PROPERTYFILE = "vtool.properties";
116 	private final String PROPERTYTOOLNAME = "vtool.name";
117 	private final String PROPERTYVERSION = "vtool.version";
118 	private final String PROPERTYDATE = "vtool.date";
119 	private final String PROPERTYCOPYRIGHT = "vtool.copyright";
120 
121 	/*** 
122 	 * Default constructor.
123 	 */
124 	public VTool() {
125 		alias = false;
126 		config = null;
127 		dictionaries = new ArrayList();
128 		userSpecifiedDictionaries = null;
129 //		dataObj = true;
130 		noFiles = null;
131 		noDirs = null;
132 		followPtrs = true;
133 		targets = new ArrayList();
134 		includePaths = null;
135 		force = false;
136 		progress = false;
137 		regexp = null;
138 		recursive = true;
139 		rptFile = null;
140 		rptStyle = "full";
141 		severity = "Warning";
142 		logFile = null;
143 		showLog = false;
144 		verbose = 2;
145 		currDir = null;
146 		buildOpts();
147 		dictionaryPassed = true;
148 	}
149 	
150 	/***
151 	 * Show the version and disclaimer notice for VTool. 
152 	 * @throws IOException 
153 	 *
154 	 */	
155 	public void showVersion() throws IOException {
156 		URL propertyFile = this.getClass().getResource(PROPERTYFILE);
157 		Properties p  = new Properties();
158 		InputStream in = null;
159 		try {
160 			in = propertyFile.openStream();
161 			p.load(in);
162 		} finally {
163 			in.close();
164 		}
165 		System.err.println("\n" + p.get(PROPERTYTOOLNAME));
166 		System.err.println(p.get(PROPERTYVERSION));
167 		System.err.println("Release Date: " + p.get(PROPERTYDATE));
168 		System.err.println(p.get(PROPERTYCOPYRIGHT) + "\n");
169 	}
170 	
171 	/***
172 	 * Display VTool usage and help information
173 	 *
174 	 */
175 	public void showHelp() {
176 		HelpFormatter formatter = new HelpFormatter();
177 		formatter.printHelp(37, "VTool", null, options, null);
178 	}
179 	
180 	/***
181 	 * Builds the set of configurable parameters for VTool.
182 	 */
183 	private void buildOpts() {
184 		options = new Options();
185 		ToolsOption opt = null;
186 		
187 		options.addOption(new ToolsOption(ALIAS[SHORT], ALIAS[LONG], WHATIS_ALIAS));
188 		options.addOption(new ToolsOption(FOLLOW[SHORT], FOLLOW[LONG],WHATIS_FOLLOW));
189 		options.addOption(new ToolsOption(HELP[SHORT], HELP[LONG], WHATIS_HELP));
190 		options.addOption(new ToolsOption(PARTIAL[SHORT], PARTIAL[LONG], WHATIS_PARTIAL)); 
191 		options.addOption(new ToolsOption(LOCAL[SHORT], LOCAL[LONG], WHATIS_LOCAL)); 
192 		options.addOption(new ToolsOption(PROGRESS[SHORT], PROGRESS[LONG], WHATIS_PROGRESS)); 
193 		options.addOption(new ToolsOption(VERSION[SHORT], VERSION[LONG], WHATIS_VERSION)); 	
194 		
195 		// These are options that require an argument
196 		
197 		// Option to specify a configuration file
198 		opt = new ToolsOption(CONFIG[SHORT], CONFIG[LONG], WHATIS_CONFIG);
199 		opt.hasArg(CONFIG[ARGNAME], String.class);
200 		options.addOption(opt);
201 		
202 		// Option to specify the PSDD and any local dictionaries
203 		opt = new ToolsOption(DICT[SHORT], DICT[LONG], WHATIS_DICT);
204 		opt.hasArgs(DICT[ARGNAME], String.class);
205 		options.addOption(opt);
206 		
207 		// Option to specify a file containing a list of file extensions to ignore
208 		opt = new ToolsOption(IGNOREFILE[SHORT], IGNOREFILE[LONG],WHATIS_IGNOREFILE);
209 		opt.hasArgs(IGNOREFILE[ARGNAME], String.class);
210 		options.addOption(opt);
211 		
212 		// Option to specify the label(s) to validate
213 		opt = new ToolsOption(TARGET[SHORT], TARGET[LONG], WHATIS_TARGET);
214 		opt.hasArgs(TARGET[ARGNAME], String.class);
215 		options.addOption(opt);
216 		
217 		// Option to specify a pattern to match against the input directory to be validated
218 		opt = new ToolsOption(REGEXP[SHORT], REGEXP[LONG], WHATIS_REGEXP);
219 		opt.hasArgs(REGEXP[ARGNAME], String.class);
220 		options.addOption(opt);		
221 		
222 		// Option to specify a path to the Pointer files	
223 		opt = new ToolsOption(INCLUDES[SHORT], INCLUDES[LONG], WHATIS_INCLUDES);
224 		opt.hasArgs(INCLUDES[ARGNAME], String.class);
225 		options.addOption(opt);
226 
227 		//Option to specify a file containing a list of directories to ignore
228 		opt = new ToolsOption(IGNOREDIR[SHORT], IGNOREDIR[LONG], WHATIS_IGNOREDIR);
229 		opt.hasArgs(IGNOREDIR[ARGNAME], String.class);
230 		options.addOption(opt);
231 		
232 		// Option to specify the log file name
233 		opt = new ToolsOption(LOG[SHORT], LOG[LONG], WHATIS_LOG);
234 		opt.hasArg(LOG[ARGNAME], String.class, true);
235 		options.addOption(opt);
236 		
237 		//Option to specify the report file name
238 		opt = new ToolsOption(REPORT[SHORT], REPORT[LONG], WHATIS_REPORT);
239 		opt.hasArg(REPORT[ARGNAME], String.class);
240 		options.addOption(opt);
241 				
242 		// Option to specify how detail the reporting should be
243 		opt = new ToolsOption(RPTSTYLE[SHORT], RPTSTYLE[LONG], WHATIS_RPTSTYLE);
244 		opt.hasArg(RPTSTYLE[ARGNAME], String.class);
245 		options.addOption(opt);
246 		
247 		// Option to specify the severity level and above
248 		opt = new ToolsOption(VERBOSE[SHORT], VERBOSE[LONG], WHATIS_VERBOSE);
249 		opt.hasArg(VERBOSE[ARGNAME], short.class);
250 		options.addOption(opt);	
251 	}
252 	
253 	/***
254 	 * Parses the VTool command-line.
255 	 * @param argv arguments given on the command-line
256 	 * @throws ApplicationException 
257 	 */
258 	public CommandLine parseLine(String[] argv) throws ApplicationException {
259 		CommandLineParser parser = new GnuParser();
260 		try {
261 			return parser.parse(options, argv);
262 		}
263 		catch( ParseException exp ) {
264 			throw new ApplicationException("Command line parser failed.\n\nReason: " + exp.getMessage() );
265 		}
266 	}
267 	
268 	/*** 
269 	 * Queries the VTool command-line.
270 	 * @throws ApplicationException 
271 	 * @throws MalformedURLException 
272 	 * @throws SystemException 
273 	 *
274 	 */
275 	public void queryCmdLine(CommandLine cmd) throws ApplicationException, 
276 	                                                 MalformedURLException, SystemException {
277 		
278 		// Check if the help flag was set
279 		if (cmd.hasOption(HELP[SHORT])) {
280 			showHelp();
281 			System.exit(SUCCESS);
282 		} else if(cmd.hasOption(VERSION[SHORT])) {
283 		// Check if the flag to display the version number and disclaimer
284 		// notice was set
285 			try {
286 				showVersion();
287 			} catch (IOException i) {
288 				throw new SystemException(i.getMessage());
289 			}
290 			System.exit(SUCCESS);
291 		}
292 		else {
293 			// Check if a configuration file was specified
294 			if (cmd.hasOption(CONFIG[SHORT])) {
295 				URL file = Utility.toURL(cmd.getOptionValue(CONFIG[SHORT]));
296 				readConfigFile(file);
297 			}
298 			// Check for the 'l' flag to specify a file where the log will be
299 			// written to
300 			if (cmd.hasOption(LOG[SHORT])) {
301 				if (cmd.getOptionValue(LOG[SHORT]) != null)
302 					setLogFile(new File(cmd.getOptionValue(LOG[SHORT])));
303 				else
304 					setShowLog(true);
305 			}
306 			// verbose flag
307 			if (cmd.hasOption(VERBOSE[SHORT])) {
308 				try {
309 					setVerbose(Short.parseShort(cmd.getOptionValue(VERBOSE[SHORT])));
310 				}
311 				catch(NumberFormatException ne) {
312 					throw new ApplicationException("Problems parsing value for the 'v' flag: " 
313 							+ cmd.getOptionValue(VERBOSE[SHORT]));
314 				}
315 				catch(IllegalArgumentException ae) {
316 					throw new ApplicationException(ae.getMessage());
317 				}		
318 			}
319 			
320 			List targetList = new ArrayList();
321 			//Grab the targets specified implicitly
322 			for(Iterator i=cmd.getArgList().iterator(); i.hasNext();) {
323 				String []values = i.next().toString().split(",");
324 				for(int index=0; index<values.length; index++)
325 					targetList.add(values[index].trim());
326 			}
327 			//Grab the targets specified explicitly
328 			if (cmd.hasOption(TARGET[SHORT])) {
329 				targetList.addAll(
330 						Arrays.asList(cmd.getOptionValues(TARGET[SHORT])));
331 			}
332 			if(!targetList.isEmpty()) {
333 				setTargets(targetList);
334 			}
335 			
336 			// Check to see if aliasing is turned on
337 			if (cmd.hasOption(ALIAS[SHORT]))
338 				setAlias(true);
339 			// Check for the flag that indicates whether to follow pointers
340 			if (cmd.hasOption(FOLLOW[SHORT]))
341 				setFollowPtrs(false);
342 			// Check for the include paths to indicate the paths to search for 
343 			// when following pointers
344 			if (cmd.hasOption(INCLUDES[SHORT]))
345 				setIncludePaths(
346 						Arrays.asList(cmd.getOptionValues(INCLUDES[SHORT])));
347 			// TODO: Check later when data object validation is implemented
348 			// Check to see if data object validation is set
349 //			if (cmd.hasOption("O")) 
350 //				setDataObj(false);
351 			if (cmd.hasOption(PROGRESS[SHORT]))
352 				setProgress(true);
353 			// Check to see if VTool will not recurse down a directory tree
354 			if (cmd.hasOption(LOCAL[SHORT]))
355 				setRecursive(false);
356 			// Check to see if regular expressions were set
357 			if (cmd.hasOption(REGEXP[SHORT]))
358 				setRegexp(Arrays.asList(cmd.getOptionValues(REGEXP[SHORT])));
359 			// Check to get file that contains file patterns to ignore
360 			if (cmd.hasOption(IGNOREFILE[SHORT])) {
361 				setNoFiles(Arrays.asList(
362 						cmd.getOptionValues(IGNOREFILE[SHORT])));
363 			}
364 			// Check to get file that contains directory patterns to ignore
365 			if (cmd.hasOption(IGNOREDIR[SHORT])) {
366 				setNoDirs(Arrays.asList(cmd.getOptionValues(IGNOREDIR[SHORT])));
367 			}
368 			// Check to get the dictionary file(s) 
369 			if (cmd.hasOption(DICT[SHORT]))
370 				setDictionaries(Arrays.asList(cmd.getOptionValues(DICT[SHORT])));
371 			// Check to see what type of reporting style the report will have
372 			if (cmd.hasOption(RPTSTYLE[SHORT]))				
373 				setRptStyle(cmd.getOptionValue(RPTSTYLE[SHORT]));
374 			// Check to see where the human-readable report will go
375 			if (cmd.hasOption(REPORT[SHORT]))
376 				setRptFile(new File(cmd.getOptionValue(REPORT[SHORT])));
377 			// Check to see if standalone label fragment validation is enabled
378 			if (cmd.hasOption(PARTIAL[SHORT]))
379 				setForcePartial(true);
380 		}
381 	}
382 	
383 	/***
384 	 * Set aliasing flag.
385 	 * @param a 'false' if aliasing should be turned off, 
386 	 * 'true' otherwise
387 	 */
388 	public void setAlias(boolean a) {
389 		this.alias = a;
390 	}
391 	
392 /*
393 	public void setDataObj(boolean d) {
394 		this.dataObj = d;
395 	}
396 */
397 
398 	/***
399 	 * Set the dictionary file names passed into VTool .
400 	 * @param d a List object of dictionary files
401 	 * @throws ApplicationException 
402 	 */
403 	public void setDictionaries(List d) throws ApplicationException {
404 		URL url = null;
405 		this.dictionaries.clear();
406 		List list = new ArrayList(d);
407 		
408 		while(list.remove("") == true);
409 		for(Iterator i=list.iterator(); i.hasNext();) {
410 			try {
411 				url = Utility.toURL(i.next().toString());
412 				url.openStream().close();
413 				this.dictionaries.add(url);
414 			} catch(IOException eX) {
415 				throw new ApplicationException(eX.getMessage());
416 			}
417 		}
418 		//This is used for the header information in the report.
419 		userSpecifiedDictionaries = list;
420 	}
421 	
422 	/***
423 	 * Set the flag that determines whether to follow pointers found in
424 	 * a label.
425 	 * @param f 'true' to follow, 'false' otherwise
426 	 */
427 	public void setFollowPtrs(boolean f) {
428 		this.followPtrs = f;
429 	}
430 	
431 	/***
432 	 * Set the flag that determines whether to validate standalone label.
433 	 * fragments
434 	 * @param f 'true' to enable, 'false' otherwise
435 	 */
436 	public void setForcePartial(boolean f) {
437 		this.force = f;
438 	}
439 	
440 	/***
441 	 * Set the paths to search for files referenced by pointers. 
442 	 * <p>
443 	 * Default is to always look first in the same directory
444 	 * as the label, then search specified directories.
445 	 * @param i List of paths
446 	 */
447 	public void setIncludePaths(List i) {
448 		this.includePaths = new ArrayList(i);
449 		while(this.includePaths.remove(""));
450 	}
451 	
452 	/***
453 	 * Set the flag to ignore specified directories.
454 	 * @param d a text file containing a list of directories and/or directory
455 	 *  patterns to ignore during validation. The names must be listed one
456 	 *  name per line.
457 	 */
458 	public void setNoDirs(List d) {
459 		this.noDirs = new ArrayList(d);
460 		while(this.noDirs.remove(""));
461 	}
462 	
463 	/***
464 	 * Set the flag to ignore specified files.
465 	 * @param f a list of files/file patterns to ignore during validation.
466 	 */
467 	public void setNoFiles(List f) {
468 		this.noFiles = new ArrayList(f);
469 		while(this.noFiles.remove(""));
470 	}
471 	
472 	/***
473 	 * Set the file name for the machine-readable log.
474 	 * @param f file name of the log
475 	 */
476 	public void setLogFile(File f) {
477 		this.logFile = f;
478 	}
479 	
480 	/***
481 	 * Set the flag to write the log to standard out.
482 	 * @param l 
483 	 */
484 	public void setShowLog(boolean l) {
485 		this.showLog = l;
486 	}
487 	
488 	/***
489 	 * Set the file for the human-readable report.
490 	 * @param f file name
491 	 */
492 	public void setRptFile(File f) {
493 		this.rptFile = f;
494 	}
495 	
496 	/***
497 	 * Set the output style for the report.
498 	 * @param style 'sum' for a summary report, 'min' for a minimal report,
499 	 *  and 'full' for a full report
500 	 * @throws ApplicationException 
501 	 */
502 	public void setRptStyle(String style) throws ApplicationException {
503 		if ( (style.equalsIgnoreCase("sum") == false) &&
504 				(style.equalsIgnoreCase("min") == false) &&
505 				(style.equalsIgnoreCase("full") == false) ) {
506 				throw new ApplicationException(
507 						"Invalid value entered for 's' flag. Value can only "
508 						+ "be either 'full', 'sum', or 'min'");
509 		}
510 		this.rptStyle = style;
511 	}
512 	
513 	/***
514 	 * Set the progress reporting flag.
515 	 * @param p
516 	 */
517 	public void setProgress(boolean p) {
518 		this.progress = p;
519 	}
520 	
521 	/***
522 	 * Set the patterns flag.
523 	 * @param e a List of patterns to be matched when searching for files to
524 	 *  validate in a directory
525 	 */
526 	public void setRegexp(List e) {
527 		this.regexp = new ArrayList(e);
528 		while(this.regexp.remove(""));
529 	}
530 	
531 	/***
532 	 * Set the recursive flag.
533 	 * @param r 'true' to recursively traverse down a directory and all its
534 	 *  sub-directories, 'false' otherwise
535 	 */
536 	public void setRecursive(boolean r) {
537 		this.recursive = r;
538 	}
539 	
540 	/***
541 	 * Set the targets flag.
542 	 * @param t a List of files, URLs, and/or directories to be validated
543 	 */
544 	public void setTargets(List t) {
545 		this.targets = new ArrayList(t);
546 		while(this.targets.remove(""));
547 	}
548 	
549 	/***
550 	 * Set the verbosity level and above to include in the reporting.
551 	 * @param v '1' for info, '2' for warnings, and '3' for errors
552 	 * @throws ApplicationException 
553 	 */
554 	public void setVerbose(short v) throws ApplicationException {
555 		if (v < 1 || v > 3) {
556 			throw new ApplicationException(
557 					"Invalid value entered for 'v' flag. "
558 					+ "Valid values can only be 1, 2, or 3");
559 		}
560 		verbose = v;
561 
562 		if (verbose == 1)
563 			severity = new String("Info");
564 		else if (verbose == 2)
565 			severity = new String("Warning");
566 		else if (verbose == 3)
567 			severity = new String("Error");
568 	}
569 	
570 	/***
571 	 * Reads a configuration file to set the default behaviors for VTool.
572 	 * <p>
573 	 * Flags set on the command-line will override flags set in the 
574 	 * configuration file
575 	 * 
576 	 * @param file a file containing keyword/value statements
577 	 * @throws ApplicationException 
578 	 */
579 	public void readConfigFile(URL file) throws ApplicationException {
580 		Configuration config = null;
581 		AbstractConfiguration.setDelimiter(',');
582 		
583 		try {
584 			config = new PropertiesConfiguration(file);
585 		} catch(ConfigurationException eX) {
586 			throw new ApplicationException(eX.getMessage());
587 		}
588 		
589 		try {
590 			if (config.isEmpty())
591 				throw new ApplicationException("Configuration file is empty: "
592 						+ file.toString());
593 			if (config.containsKey(ALIASKEY))
594 				setAlias(config.getBoolean(ALIASKEY));
595 			//TODO: Get Data Object key value when data object validation is implemented
596 //			if (config.containsKey(DATAOBJKEY))
597 //				setDataObj(config.getBoolean(DATAOBJKEY));
598 			if (config.containsKey(DICTKEY))
599 				setDictionaries(config.getList(DICTKEY));
600 			if (config.containsKey(FORCEKEY))
601 				setForcePartial(config.getBoolean(FORCEKEY));
602 			if (config.containsKey(FOLLOWKEY))
603 				setFollowPtrs(config.getBoolean(FOLLOWKEY));
604 			if (config.containsKey(IGNOREDIRKEY)) {
605 				setNoDirs(config.getList(IGNOREDIRKEY));
606 				// Removes quotes surrounding each pattern being specified
607 				for(int i=0; i < this.noDirs.size(); i++) {
608 					this.noDirs.set(i, 
609 							this.noDirs.get(i).toString().replace('"', ' ').trim());
610 				}
611 			}
612 			if (config.containsKey(IGNOREFILEKEY)) {
613 				setNoFiles(config.getList(IGNOREFILEKEY));
614 				// Removes quotes surrounding each pattern being specified
615 				for(int i=0; i < noFiles.size(); i++) {
616 					this.noFiles.set(i, 
617 							this.noFiles.get(i).toString().replace('"', ' ').trim());
618 				}
619 			}
620 			if (config.containsKey(INCLUDESKEY))
621 				setIncludePaths(config.getList(INCLUDESKEY));
622 			if (config.containsKey(LOGKEY)) {
623 				String logValue = new String(config.getProperty(LOGKEY).toString());
624 				// Check for a boolean value or a file name
625 				if(logValue.equalsIgnoreCase("true") 
626 						|| logValue.equalsIgnoreCase("false")) {
627 					setShowLog(Boolean.valueOf(logValue).booleanValue());
628 				}
629 				else
630 					setLogFile(new File(logValue));
631 			}
632 			if (config.containsKey(PROGRESSKEY))
633 				setProgress(config.getBoolean(PROGRESSKEY));
634 			if (config.containsKey(RECURSEKEY))
635 				setRecursive(config.getBoolean(RECURSEKEY));
636 			if (config.containsKey(REGEXPKEY)) {
637 				setRegexp(config.getList(REGEXPKEY));
638 				// Removes quotes surrounding each pattern being specified
639 				for(int i=0; i < this.regexp.size(); i++) {
640 					this.regexp.set(i, 
641 							this.regexp.get(i).toString().replace('"', ' ').trim());
642 				}
643 			}
644 			if (config.containsKey(REPORTKEY))
645 				setRptFile(new File(config.getString(REPORTKEY)));
646 			if (config.containsKey(STYLEKEY))
647 				setRptStyle(config.getString(STYLEKEY));
648 			if (config.containsKey(TARGETKEY))
649 				setTargets(config.getList(TARGETKEY));
650 			if (config.containsKey(VERBOSEKEY))
651 				setVerbose(config.getShort(VERBOSEKEY));
652 		} catch(ConversionException eX) {
653 			throw new ApplicationException(eX.getMessage());
654 		}
655 	}
656 	
657 	/***
658 	 * Routine to store a message into the logger.
659 	 * @param level The severity level of the message.
660 	 * @param msg The message to log.
661 	 */
662 	public void logMessage(Level level, String msg) {
663 		logMessage(level, msg, null);
664 	}
665 	
666 	/***
667 	 * Routine to store a message into the logger.
668 	 * @param level The severity level of the message.
669 	 * @param msg The message to log.
670 	 * @param file The file name associated with the message being logged.
671 	 */
672 	public void logMessage(Level level, String msg, String file) {
673 		log.log(new ToolsLogRecord(level, msg, file));
674 	}
675 	
676 	/***
677 	 * Logs report header information such as version of the tool, 
678 	 * execution time, and flag settings.
679 	 * @throws SystemException 
680 	 * @throws IOException 
681 	 *
682 	 */
683 	public void logRptHeader() throws SystemException, IOException {
684 		ToolsTime time = new ToolsTime();
685 
686 		//TODO: Print out data object validation
687 		URL propertyFile = this.getClass().getResource(PROPERTYFILE);
688 		Properties p  = new Properties();
689 		InputStream in = null;
690 		try {
691 			in = propertyFile.openStream();
692 			p.load(in);
693 		} finally {
694 			in.close();
695 		}
696 		String version = p.getProperty(PROPERTYVERSION).replaceFirst("Version", "").trim();
697 		logMessage(ToolsLevel.CONFIGURATION, "Version                   " + version);
698 		try {
699 			logMessage(ToolsLevel.CONFIGURATION, 
700 				"Date                      " 
701 				+ time.getTime(new SimpleDateFormat("EEE, MMM dd yyyy 'at' hh:mm:ss a")));
702 		} catch(IllegalArgumentException eX) {
703 			throw new SystemException(eX.getMessage());
704 		}
705 		//Indicate level of validation
706 		String validationLevel = null;
707 		if(dictionaries.isEmpty())
708 			validationLevel = "Syntactic only";
709 		else
710 			validationLevel = "Syntactic and Semantic";
711 		logMessage(ToolsLevel.CONFIGURATION, "Level of Validation       " + validationLevel);
712 		
713 		if(targets != null) {
714 			logMessage(ToolsLevel.PARAMETER, 
715 					"Target(s)                      " + targets);
716 		}
717 		if (userSpecifiedDictionaries != null) {
718 			logMessage(ToolsLevel.PARAMETER, 
719 					"Dictionary File(s)             " + userSpecifiedDictionaries);
720 		}
721 		logMessage(ToolsLevel.PARAMETER, 
722 				"Aliasing                       " + alias);
723 		logMessage(ToolsLevel.PARAMETER, 
724 				"Directory Recursion            " + recursive);
725 		logMessage(ToolsLevel.PARAMETER, 
726 				"Follow Pointers                " + followPtrs);
727 		logMessage(ToolsLevel.PARAMETER, 
728 				"Validate Standalone Fragments  " + force);
729 		
730 		if (logFile != null) {
731 			logMessage(ToolsLevel.PARAMETER, 
732 					"Log File                       " + logFile);
733 		}
734 		if (rptFile != null) {
735 			logMessage(ToolsLevel.PARAMETER, 
736 					"Report File                    " + rptFile);
737 		}
738 		if (rptStyle != null) {
739 		    logMessage(ToolsLevel.PARAMETER, 
740 		    		"Report Style                   " + rptStyle);
741 		}
742 		if (severity != null) {
743 			logMessage(ToolsLevel.PARAMETER, 
744 					"Severity Level                 " + severity);
745 		}
746 		if (includePaths != null) {
747 			logMessage(ToolsLevel.PARAMETER, 
748 					"Include Path(s)                " + includePaths);
749 		}
750 		if (config != null) {
751 			logMessage(ToolsLevel.PARAMETER, 
752 					"Configuration File             " + config);
753 		}
754 		if (regexp != null) {
755 			logMessage(ToolsLevel.PARAMETER, 
756 					"Files Patterns                 " + regexp);
757 		}
758 		if (noDirs != null) {
759 			logMessage(ToolsLevel.PARAMETER, 
760 					"Excluded Directories           " + noDirs);
761 		}
762 		if (noFiles != null) {
763 			logMessage(ToolsLevel.PARAMETER, 
764 					"Excluded Files                 " + noFiles);
765 		}
766 		logMessage(ToolsLevel.PARAMETER, 
767 				"Progress Reporting             " + progress);
768 	}
769 	
770 	/***
771 	 * Configures the logger appropriately.
772 	 * <p>
773 	 * If a log file was specified on the command-line, the log
774 	 * will be written to that file. If the log flag was
775 	 * specified with no file spec, then the log will be written
776 	 * to standard out. Otherwise, the log will be written to
777 	 * memory (ByteArrayOutputStream). 
778 	 * @throws SystemException 
779 	 * @throws ApplicationException 
780 	 *
781 	 */
782 	public void setupLogger() 
783 	           throws ApplicationException, SystemException {
784 			
785 		Logger logger = Logger.getLogger("");
786 		logger.setLevel(Level.ALL);
787 		
788 		Handler []handler = logger.getHandlers();
789 		for(int i = 0; i < logger.getHandlers().length; i++) 
790 			logger.removeHandler(handler[i]);
791 		
792 		HandlerFactory hf = HandlerFactory.getInstance();
793 		Handler reportHandler = null;
794 		try {
795 			//Creates a handler for the XML log
796 			if(logFile != null && !showLog) {
797 				logger.addHandler(new ToolsFileHandler(logFile.toString(), new ToolsLogFormatter()));
798 			}
799 			else if(showLog && logFile == null) {
800 				logger.addHandler(new ToolsStreamHandler(System.out, new ToolsLogFormatter()));
801 			}	
802 			reportHandler = hf.newInstance(rptFile, rptStyle, severity);
803 			logger.addHandler(reportHandler);
804 		} catch (SecurityException s) {
805 			throw new SystemException(s.getMessage());
806 		} catch (IOException io) {
807 			throw new ApplicationException(io.getMessage());
808 		} catch (UnknownHandlerConfigurationException uh) {
809 			throw new ApplicationException(uh.getMessage());
810 		}		
811 	}
812 	
813 	/***
814 	 * Parse the dictionary files.
815 	 * 
816 	 * @param dictionary a list of dictionary URLs
817 	 * @return a Dictionary object that includes all the dictionary
818 	 *  information from all the dictionary files passed in.
819 	 * @throws ApplicationException
820 	 * @throws gov.nasa.pds.tools.label.parser.ParseException 
821 	 * @throws UnknownLabelStatusException 
822 	 */
823 	public Dictionary readDictionaries(List dictionary) throws
824 	                          ApplicationException {
825 		Dictionary dict = null;
826 		URL url = null;
827 		Iterator i = dictionary.iterator();
828 		
829 		try {
830 			url = (URL) i.next();
831 			dict = DictionaryParser.parse(url, alias);
832             logMessage(ToolsLevel.CONFIGURATION, "Dictionary version        \n"
833             		           + dict.getInformation(), url.toString());
834             //Log dictionary status only upon a FAIL
835             if(dict.getStatus().equals(FAIL)) {
836             	throw new gov.nasa.pds.tools.label.parser.ParseException(
837             			"Dictionary parsing failed: " + url.toString());
838             } 
839 			//Parse the rest of the dictionaries
840 			while (i.hasNext()) {
841 				url = (URL) i.next();
842 				Dictionary mergeDict = DictionaryParser.parse(url, alias);
843 				dict.merge(mergeDict, true);
844 				logMessage(ToolsLevel.CONFIGURATION, "Dictionary version        \n"
845 					+ mergeDict.getInformation(), url.toString());
846 				//Log dictionary status only upon a FAIL
847 				if(mergeDict.getStatus().equals(FAIL)) {
848 	            	throw new gov.nasa.pds.tools.label.parser.ParseException(
849 	            			"Dictionary parsing failed: " + url.toString());
850 				}
851 			}
852 		} catch (IOException iEx) {
853 			throw new ApplicationException(iEx.getMessage());
854 		} catch (gov.nasa.pds.tools.label.parser.ParseException pe) {
855 			logMessage(ToolsLevel.NOTIFICATION, "FAIL", url.toString());
856 			dictionaryPassed = false;
857 		}
858 		return dict;
859 	}
860 	
861 	/***
862 	 * Sets up the include paths and flag to follow pointers in
863 	 * the parser.
864 	 * @param parser The label parser to set properties for
865 	 * @throws ApplicationException 
866 	 */
867 	private void setParserProps(LabelParser parser) 
868 	                                  throws ApplicationException {
869 		URL path = null;
870 		if (includePaths != null) {
871 			for( Iterator i = includePaths.iterator(); i.hasNext(); ) {
872 				try {
873 					path = Utility.toURL(i.next().toString());
874 					path.openStream().close();
875 				} catch (Exception eX) {
876 					throw new ApplicationException(eX.getMessage());
877 				}
878 				parser.addIncludePath(path);
879 			}
880 		}
881 
882 		if (followPtrs == false)
883 			parser.getProperties().setProperty("parser.pointers", "false");
884 	}
885 	
886 	/***
887 	 * Processes a target.
888 	 * 
889 	 * @param target The file or URL to process
890 	 * @param getSubDirs 'True' to look for sub-directories, 'false' otherwise
891 	 * @return A FileList object containing the sub-directories and files
892 	 *  found in the target, if any
893 	 * @throws ApplicationException 
894 	 * @throws SystemException 
895 	 */
896 	public FileList processTarget(String target, boolean getSubDirs) 
897 						throws ApplicationException, SystemException {		
898 		FileListGenerator fileGen = new FileListGenerator();
899 		FileList list = new FileList();
900 		fileGen.setFilters(regexp, noFiles, noDirs);
901 		
902 		try {
903 			list = fileGen.visitTarget(target, getSubDirs);
904 		}catch(IOException io) {
905 			throw new ApplicationException(io.getMessage());
906 		}catch (Exception eX) {
907 			throw new SystemException(eX.getMessage());
908 		}
909 		return list;
910 	}
911 	
912 	/***
913 	 * Validate labels, performing only syntactic validation.
914 	 * 
915 	 * @param targets
916 	 * @return A ValidationRecord containing the results of the validation
917 	 * run.
918 	 * @throws MalformedURLException
919 	 * @throws ApplicationException
920 	 * @throws SystemException
921 	 * @throws UnknownLabelStatusException
922 	 */
923 	public ValidationRecord validateLabels(List targets) 
924 	        throws MalformedURLException, ApplicationException,
925 	               SystemException, UnknownLabelStatusException {
926 		return validateLabels(targets, null);
927 	}
928 	
929 	/***
930 	 * Validate labels, performing both syntactic and semantic validation.
931 	 * If the target is a directory, this method will validate all labels
932 	 * found within the directory and its sub-directories. If recursion
933 	 * is turned OFF, then sub-directories will not be looked into.
934 	 * 
935 	 * @param targets a list of files, directories, and/or URLs.
936 	 * @param dict the dictionary file.
937 	 * @return A ValidationRecord containing the results of the validation.
938 	 * @throws SystemException 
939 	 * @throws ApplicationException 
940 	 * @throws MalformedURLException 
941 	 * @throws UnknownLabelStatusException 
942 	 */	
943 	public ValidationRecord validateLabels(List targets, Dictionary dict) 
944 	            throws ApplicationException, SystemException, 
945 	                   MalformedURLException, UnknownLabelStatusException {
946 		
947 		ValidationRecord record = new ValidationRecord();
948 		
949 		for (Iterator i1 = targets.iterator(); i1.hasNext();) {
950 			FileList fileList = new FileList();
951 			fileList = processTarget(i1.next().toString(), recursive);		
952 			for (Iterator i2 = fileList.getFiles().iterator(); i2.hasNext();) {
953 				URL target = Utility.toURL(i2.next().toString());				
954 				record.add(validateLabel(target, dict));
955 			}		
956 			if (!fileList.getDirs().isEmpty())
957 				record.add(validateLabels(fileList.getDirs(), dict));
958 		}
959 			
960 		return record;
961 	}
962 	
963 	/***
964 	 * Validate a label file.
965 	 * 
966 	 * @param url The URL of the file to be validated
967 	 * @param dict a Dictionary object needed for semantic validation. If null,
968 	 *             only syntactic validation will be performed.
969 	 * @return A ValidationRecord object containing the results of the validation.
970 	 * @throws ApplicationException 
971 	 * @throws SystemException 
972 	 * @throws UnknownLabelStatusException 
973 	 */	
974 	public ValidationRecord validateLabel(URL url, Dictionary dict) 
975 						        throws ApplicationException, SystemException,
976 						                         UnknownLabelStatusException {		
977 		LabelParserFactory factory = LabelParserFactory.getInstance();
978 		LabelParser parser = factory.newLabelParser();
979 		Label label = null;
980 		setParserProps(parser);
981 		
982 		if (progress)
983 			showProgress(url);
984 		
985 		ValidatorFactory vf = ValidatorFactory.getInstance();
986 		Validator validator = vf.newInstance(parser, url, force);
987 		try {
988 			if(dict == null)
989 				label = validator.validate(url);
990 			else
991 				label = validator.validate(url, dict);
992 		} catch (gov.nasa.pds.tools.label.parser.ParseException pEx) {
993 			label = new Label();
994 			label.setStatus(Label.SKIP);
995 			label.incrementWarnings();
996 		} catch (ValidatorException ve) {
997 			label = new Label();
998 			label.setStatus(Label.SKIP);
999 			label.incrementWarnings();
1000 		}
1001 		return new ValidationRecord(label.getStatus(), label.getNumWarnings(), label.getNumErrors());
1002 	}
1003 	
1004 	/***
1005 	 *  Prints out the current directory being validated and
1006 	 *  represents each file being validated by an asterisk.
1007 	 *  This is used when progress reporting is enabled.
1008 	 * 
1009 	 * @param file The URL being validated
1010 	 * @throws SystemException 
1011 	 */
1012 	public void showProgress(URL file) throws SystemException {
1013 		URL dir = null;
1014 		try {
1015 			dir = new URL(file.toString().
1016 						substring(0, file.toString().lastIndexOf("/")));
1017 		} catch (MalformedURLException eX) {
1018 			throw new SystemException(eX.getMessage());
1019 		}
1020 
1021 		if (dir.toString().equals(currDir)) {
1022 			System.err.print(FILE_REP);
1023 		}
1024 		else {
1025 			currDir = new String(dir.toString());
1026 			System.err.println("\nValidating file(s) in: " + currDir);
1027 			System.err.print(FILE_REP);
1028 		}
1029 	}
1030 	  
1031     /***
1032      * Closes the handlers that were set for the logger.
1033      *
1034      */
1035 	public void closehandle() {
1036 		Logger logger = Logger.getLogger("");
1037 		Handler handlers[] = logger.getHandlers();
1038 		for(int i=0; i<handlers.length; i++) {
1039 			handlers[i].close();
1040 		}
1041 	}
1042 	
1043 	/***
1044 	 * Implementation to perform automated PDS validation.
1045 	 * 
1046 	 * <p>
1047 	 * The main calls the following methods (in this order):
1048 	 * <p>
1049 	 * To setup the flags and parse the command-line options:
1050 	 * <ul> 
1051 	 * <li>parseLine</li>
1052 	 * <li>queryCmdLine</li>
1053 	 * </ul>
1054 	 * 
1055 	 * <p>
1056 	 * To setup the logger and log the report header information:
1057 	 * <ul>
1058 	 * <li>setupLogger</li>
1059 	 * <li>logRptHeader</li>
1060 	 * </ul>
1061 	 * 
1062 	 * <p>
1063 	 * To perform validation:
1064 	 * <ul>
1065 	 * <li>readDictionaries (if the PSDD was passed in)</li>
1066 	 * <li>validateLabels</li>
1067 	 * </ul>
1068 	 * 
1069 	 * <p>
1070 	 * To create the final report:
1071 	 * <ul>
1072 	 * <li>closehandle (to flush the buffers)</li>
1073 	 * <li>doReporting</li>
1074 	 * </ul>
1075 	 * 
1076 	 * <p>
1077 	 * Reporting is not generated if the log flag was specified with
1078 	 * no file spec and the report file flag was not specified
1079 	 * 
1080 	 * <p>
1081 	 * VTool returns an appropriate exit status based on validation results.
1082 	 * 
1083 	 * @param argv Arguments passed on the command-line
1084 	 */
1085 	public static void main(String[] argv) {
1086 		VTool vtool = new VTool();
1087 		ExitStatus status = new ExitStatus();
1088 
1089 		if (argv.length == 0) {
1090 			System.out.println("\nType 'VTool -h' for usage");
1091 			System.exit(SUCCESS);
1092 		}
1093 
1094 		try {
1095 			//Parse the command line
1096 			CommandLine commandLine = vtool.parseLine(argv);
1097 			// Query the command line
1098 			vtool.queryCmdLine(commandLine);
1099 			//Setup the logger
1100 			vtool.setupLogger();
1101 			// Log the report header
1102 			vtool.logRptHeader();	
1103 			
1104 			ValidationRecord record = new ValidationRecord();
1105 			if ( vtool.dictionaries.isEmpty() ) {
1106 				record = vtool.validateLabels(vtool.targets);
1107 			    status.setStatus(record);
1108 			}
1109 			else {
1110 				//If dictionary parsing fails, VTool will not
1111 				//perform the validation.
1112 				Dictionary dictionary = vtool.readDictionaries(vtool.dictionaries);
1113                 if(vtool.dictionaryPassed) {
1114                 	record = vtool.validateLabels(vtool.targets, dictionary);
1115                 	status.setStatus(record);
1116                 }
1117                 else
1118                 	status.setStatus(APPLICATION_ERROR);
1119 			}
1120 			vtool.closehandle();
1121 		
1122 			//Add extra line terminator if progress reporting was enabled
1123 			if(vtool.progress == true)
1124 				System.err.println();
1125 		
1126 		} catch(ApplicationException app) {
1127 			System.err.println(app.getMessage());
1128 			status.setStatus(APPLICATION_ERROR);
1129 		} catch(Exception e) {
1130 			System.err.println(e.getMessage());
1131 			status.setStatus(SYSTEM_ERROR);
1132 		} finally {
1133 			System.exit(status.getStatus(vtool.severity));
1134 		}
1135 	}
1136 }