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 2666 2007-05-02 15:42:28Z mcayanan $
14  //
15  
16  package gov.nasa.pds.tools;
17  
18  import gov.nasa.pds.tools.config.VToolConfigKeys;
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.flags.ToolsFlags;
24  import gov.nasa.pds.tools.label.Label;
25  import gov.nasa.pds.tools.label.parser.LabelParser;
26  import gov.nasa.pds.tools.label.parser.LabelParserFactory;
27  import gov.nasa.pds.tools.label.validate.Status;
28  import gov.nasa.pds.tools.logging.ToolsLevel;
29  import gov.nasa.pds.tools.logging.ToolsLogFormatter;
30  import gov.nasa.pds.tools.logging.ToolsLogRecord;
31  import gov.nasa.pds.tools.status.ExitStatus;
32  import gov.nasa.pds.tools.time.ToolsTime;
33  import gov.nasa.pds.tools.xsl.StyleSheet;
34  
35  import java.io.ByteArrayInputStream;
36  import java.io.ByteArrayOutputStream;
37  import java.io.File;
38  import java.io.FileInputStream;
39  import java.io.FileNotFoundException;
40  import java.io.FileOutputStream;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.io.OutputStream;
44  import java.net.MalformedURLException;
45  import java.net.URL;
46  import java.text.SimpleDateFormat;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.logging.FileHandler;
52  import java.util.logging.Handler;
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  import java.util.logging.StreamHandler;
56  
57  import javax.swing.text.BadLocationException;
58  import javax.xml.transform.Result;
59  import javax.xml.transform.Source;
60  import javax.xml.transform.Transformer;
61  import javax.xml.transform.TransformerException;
62  import javax.xml.transform.TransformerFactory;
63  import javax.xml.transform.stream.StreamResult;
64  import javax.xml.transform.stream.StreamSource;
65  
66  import org.apache.commons.cli.CommandLine;
67  import org.apache.commons.cli.CommandLineParser;
68  import org.apache.commons.cli.GnuParser;
69  import org.apache.commons.cli.HelpFormatter;
70  import org.apache.commons.cli.MissingOptionException;
71  import org.apache.commons.cli.OptionBuilder;
72  import org.apache.commons.cli.Options;
73  import org.apache.commons.cli.ParseException;
74  import org.apache.commons.cli.UnrecognizedOptionException;
75  import org.apache.commons.configuration.AbstractConfiguration;
76  import org.apache.commons.configuration.Configuration;
77  import org.apache.commons.configuration.ConfigurationException;
78  import org.apache.commons.configuration.ConversionException;
79  import org.apache.commons.configuration.PropertiesConfiguration;
80  import org.apache.commons.io.FilenameUtils;
81  
82  /***
83   * Class to perform automated validation to determine if a given data product
84   * is PDS compliant.
85   * <p>
86   * This replaces LVTool functionality.
87   *  
88   * @author mcayanan
89   * @version $Revision: 2666 $
90   *
91   *
92   */
93  public class VTool implements VToolConfigKeys, ToolsFlags, ExitStatus, 
94                                Status, StyleSheet {
95  	private final String VERSION_ID = "1.0.0";
96  	private final String FILE_REP = "*";
97  	private final String FRAG_EXT = "FMT";
98  	
99  	private static Logger log = Logger.getLogger(VTool.class.getName());
100 	private ByteArrayOutputStream byteStream;
101 	private String currDir;
102 	private boolean dictPassed;
103 	
104 	private List logHandlers;
105 	private Options options;
106 	private CommandLineParser parser;
107 	private CommandLine cmd;
108 	
109 	//TODO: Flags to be implemented: data object(-O,--no-obj),
110 	
111 	private boolean alias;
112 	private URL config;
113 //	private boolean dataObj;
114 	private List dictionaries;
115 	private List noFiles;
116 	private List noDirs;
117 	private boolean followPtrs;
118 	private List includePaths;
119 	private boolean force;
120 	private boolean progress;
121 	private List regexp;
122 	private boolean recursive;
123 	private List targets;
124 	private File logFile;
125 	private boolean showLog;
126 	private File rptFile;
127 	private String rptStyle;
128 	private short verbose;
129 	private String severity;
130 	
131 	private int goodLbls;
132 	private int badLbls;
133 	private int unkLbls;
134 	
135 	/*** 
136 	 * Default constructor.
137 	 */
138 	public VTool() {
139 		alias = false;
140 		config = null;
141 		dictionaries = null;
142 //		dataObj = true;
143 		dictPassed = true;
144 		noFiles = null;
145 		noDirs = null;
146 		followPtrs = true;
147 		targets = null;
148 		includePaths = null;
149 		force = false;
150 		progress = false;
151 		regexp = null;
152 		recursive = true;
153 		rptFile = null;
154 		rptStyle = "full";
155 		severity = "Warning";
156 		logFile = null;
157 		showLog = false;
158 		verbose = 2;
159 		
160 		currDir = null;
161 		options = new Options();
162 		parser = new GnuParser();
163 		logHandlers = new ArrayList();
164 		
165 		goodLbls = 0;
166 		badLbls = 0;
167 		unkLbls = 0;
168 	}
169 	
170 	/***
171 	 * Show the version and disclaimer notice for VTool. 
172 	 *
173 	 */	
174 	public void showVersion() {
175 		System.out.println("\nPDS Validation Tool (VTool) " + VERSION_ID);
176 		System.out.println("\nCopyright 2006-2007, by the California Institute of Technology. ALL\n"
177 				           + "RIGHTS RESERVED. United States Government Sponsorship acknowledged.\n"
178 				           + "Any commercial use must be negotiated with the Office of Technology\n"
179 				           + "Transfer at the California Institute of Technology. This software\n"
180 				           + "may be subject to U.S. export control laws. By accepting this\n"
181 				           + "software, the user agrees to comply with all applicable U.S. export\n"
182 				           + "laws and regulations. User has the responsibility to obtain export\n"
183 				           + "licenses, or other export authority as may be required before\n"
184 				           + "exporting such information to foreign countries or providing access\n"
185 				           + "to foreign persons.");
186 		System.exit(GOODRUN);
187 	}
188 	
189 	/***
190 	 * Display VTool usage and help information
191 	 *
192 	 */
193 	public void showHelp() {
194 		HelpFormatter formatter = new HelpFormatter();
195 		formatter.printHelp(37, "VTool", null, options, null);
196 		System.exit(GOODRUN);
197 	}
198 	
199 	/***
200 	 * Builds the set of configurable parameters for VTool.
201 	 */
202 	public void buildOpts() {
203 		options.addOption(ALIAS[SHORT], ALIAS[LONG], false, "Enable aliasing");
204 		options.addOption(FOLLOW[SHORT], FOLLOW[LONG], false, 
205 				"Do not follow or check for the existence of files referenced "
206 				+ "by pointer statements in a label");
207 		options.addOption(HELP[SHORT], HELP[LONG], false, "Display usage");
208 //		options.addOption("O", "no-obj", false, "Do not perform data object validation");
209 		options.addOption(PARTIAL[SHORT], PARTIAL[LONG], false, 
210 				"Force VTool to validate a label fragment");
211 		options.addOption(LOCAL[SHORT], LOCAL[LONG], false, 
212 				"Validate files only in the target directory rather than "
213 				+ "recursively traversing down the subdirectories.");
214 		options.addOption(PROGRESS[SHORT], PROGRESS[LONG], false, 
215 				"Enable progress reporting");
216 		options.addOption(VERSION[SHORT], VERSION[LONG], false, 
217 				"Display VTool version");
218 		
219 		
220 		// These are options that require an argument
221 
222 		// Option to specify a configuration file
223 		OptionBuilder.withLongOpt(CONFIG[LONG]);
224 		OptionBuilder.withDescription(
225 		  "Specify a configuration file to set the default values for VTool");
226 		OptionBuilder.hasArg();
227 		OptionBuilder.withArgName("file");
228 		OptionBuilder.withType(String.class);
229 		options.addOption(OptionBuilder.create(CONFIG[SHORT]));
230 		
231 		// Option to specify the PSDD and any local dictionaries
232 		OptionBuilder.withLongOpt(DICT[LONG]);
233 		OptionBuilder.withDescription(
234 		  "Specify the Planetary Science Data Dictionary full file name/URL "
235 		  + "and any local dictionaries to include for validation. Separate "
236 		  + "each file name with a space");
237 		OptionBuilder.hasArgs();
238 		OptionBuilder.withArgName(".full files");
239 		OptionBuilder.withValueSeparator(' ');
240 		OptionBuilder.withType(String.class);
241 		options.addOption(OptionBuilder.create(DICT[SHORT]));
242 		
243 		// Option to specify a file containing a list of file extensions to ignore
244 		OptionBuilder.withLongOpt(IGNOREFILE[LONG]);
245 		OptionBuilder.withDescription(
246 		  "Specify file patterns to ignore from validation. Separate each "
247 		  + "with a space. Patterns should be surrounded by quotes "
248 		  + "(i.e. -X \"*TAB\" \"*IMG\" or -X \"*TAB *IMG\")");
249 		OptionBuilder.hasArgs();
250 		OptionBuilder.withArgName("patterns");
251 		OptionBuilder.withValueSeparator(' ');
252 		OptionBuilder.withType(String.class);
253 		options.addOption(OptionBuilder.create(IGNOREFILE[SHORT]));
254 		
255 		// Option to specify the label(s) to validate
256 
257 		OptionBuilder.withLongOpt(TARGET[LONG]);
258 		OptionBuilder.withDescription(
259 		  "Explicitly specify the targets (label files, URLs and directories) "
260 		  + "to validate. Targets can be specified implicitly as well. "
261 		  + "(example: VTool label.lbl)");
262 		OptionBuilder.hasArgs();
263 		OptionBuilder.withArgName("labels,URLs,dirs");
264 		OptionBuilder.withType(String.class);
265 		OptionBuilder.withValueSeparator(' ');
266 		options.addOption(OptionBuilder.create(TARGET[SHORT]));
267 		
268 		// Option to specify a pattern to match against the input directory to be validated
269 		OptionBuilder.withLongOpt(REGEXP[LONG]);
270 		OptionBuilder.withDescription(
271 		  "Specify file patterns to look for when validating a directory. "
272 		  + "Separate each with a space. Patterns should be surrounded by "
273 		  + "quotes (i.e. -e \"*.LBL\" \"*.FMT\" or -e \"*.LBL *.FMT\")");
274 		OptionBuilder.hasArgs();
275 		OptionBuilder.withArgName("patterns");
276 		OptionBuilder.withValueSeparator(' ');
277 		OptionBuilder.withType(String.class);
278 		options.addOption(OptionBuilder.create(REGEXP[SHORT]));
279 		
280 		// Option to specify a path to the Pointer files		
281 		OptionBuilder.withLongOpt(INCLUDES[LONG]);
282 		OptionBuilder.withDescription(
283 		  "Specify the paths to look for files referenced by pointers in a "
284 		  + "label. Separate each with a space. Default is to always look "
285 		  + "at the same directory as the label");
286 		OptionBuilder.hasArgs();
287 		OptionBuilder.withArgName("paths");
288 		OptionBuilder.withType(String.class);
289 		OptionBuilder.withValueSeparator(' ');
290 		options.addOption(OptionBuilder.create(INCLUDES[SHORT]));
291 
292 		//Option to specify a file containing a list of directories to ignore
293 		OptionBuilder.withLongOpt(IGNOREDIR[LONG]);
294 		OptionBuilder.withDescription(
295 		  "Specify directory patterns to ignore. Separate each with a space. "
296 		  + "Patterns should be surrounded by quotes "
297 		  + "(i.e. -D \"EXTRAS\" \"LABEL\" or -D \"EXTRAS LABEL\")");
298 		OptionBuilder.hasArgs();
299 		OptionBuilder.withArgName("patterns");
300 		OptionBuilder.withValueSeparator(' ');
301 		OptionBuilder.withType(String.class);
302 		options.addOption(OptionBuilder.create(IGNOREDIR[SHORT]));
303 		
304 		// Option to specify the log file name
305 		OptionBuilder.withLongOpt(LOG[LONG]);
306 		OptionBuilder.withDescription(
307 		  "Specify the file name for the machine-readable log. A file "
308 		  + "specification is optional. If no file name is given, then "
309 		  + "the log will be written to standard out");
310 		OptionBuilder.hasOptionalArg();
311 		OptionBuilder.withArgName("file (optional)");
312 		OptionBuilder.withType(String.class);
313 		options.addOption(OptionBuilder.create(LOG[SHORT]));
314 
315 		
316 		//Option to specify the report file name
317 		OptionBuilder.withLongOpt(REPORT[LONG]);
318 		OptionBuilder.withDescription(
319 		  "Specify the file name for the human-readable report. Default is "
320 		  + "to write to standard out if this flag is not specified. This "
321 		  + "report, however, will not print to standard out if this flag is "
322 		  + "missing AND the log file flag is specified with no file name");
323 		OptionBuilder.hasArg();
324 		OptionBuilder.withArgName("file");
325 		OptionBuilder.withType(String.class);
326 		options.addOption(OptionBuilder.create(REPORT[SHORT]));
327 		
328 		// Option to specify how detail the reporting should be
329 		OptionBuilder.withLongOpt(RPTSTYLE[LONG]);
330 		OptionBuilder.withDescription(
331 		  "Specify the level of detail for the reporting. Valid values are "
332 		  + "'full' for a full view, 'min' for a minimal view and 'sum' for "
333 		  + "a summary view. Default is to see a full report if this flag is "
334 		  + "not specified");
335 		OptionBuilder.hasArg();
336 		OptionBuilder.withArgName("full|sum|min");
337 		OptionBuilder.withType(String.class);
338 		options.addOption(OptionBuilder.create(RPTSTYLE[SHORT]));
339 		
340 		// Option to specify the severity level and above
341 		OptionBuilder.withLongOpt(VERBOSE[LONG]);
342 		OptionBuilder.withDescription(
343 		  "Specify the severity level and above to include in the "
344 		  + "human-readable report: (1=Info, 2=Warning, 3=Error). "
345 		  + "Default is Warning and above (level 2)");
346 		OptionBuilder.hasArg();
347 		OptionBuilder.withArgName("1|2|3");
348 		OptionBuilder.withType(short.class);
349 		options.addOption(OptionBuilder.create(VERBOSE[SHORT]));
350 		
351 	}
352 	
353 	/***
354 	 * Parses the VTool command-line.
355 	 * @param argv arguments given on the command-line
356 	 */
357 	public void parseLine(String[] argv) {
358 		try {
359 			cmd = parser.parse(options, argv);
360 		}
361 		catch( ParseException exp ) {
362 			System.err.println(
363 					"Command line parser failed.\n\nReason: "
364 					+ exp.getMessage() );
365 			System.exit(TOOLFAILURE);
366 		}
367 	}
368 	
369 	/*** 
370 	 * Queries the VTool command-line.
371 	 *
372 	 */
373 	public void queryCmdLine() {
374 		List targetList = new ArrayList();
375 		
376 		try {
377 			// Check if the help flag was set
378 			if (cmd.hasOption(HELP[SHORT]))
379 				showHelp();
380 			// Check if the flag to display the version number and disclaimer
381 			// notice was set
382 			if (cmd.hasOption(VERSION[SHORT])) 
383 				showVersion();
384 			// Check if a configuration file was specified
385 			if (cmd.hasOption(CONFIG[SHORT])) {
386 				URL file = toURL(cmd.getOptionValue(CONFIG[SHORT]));
387 				readConfigFile(file);
388 			}
389 			// Check for the 'l' flag to specify a file where the log will be
390 			// written to
391 			if (cmd.hasOption(LOG[SHORT])) {
392 				if (cmd.getOptionValue(LOG[SHORT]) != null)
393 					setLogFile(new File(cmd.getOptionValue(LOG[SHORT])));
394 				else
395 					setShowLog(true);
396 			}
397 			// verbose flag
398 			if (cmd.hasOption(VERBOSE[SHORT])) {
399 				try {
400 					setVerbose(Short.parseShort(cmd.getOptionValue(VERBOSE[SHORT])));
401 				}
402 				catch( NumberFormatException ne) {
403 					throw new Exception("Problems parsing value for the 'v' flag: " 
404 							+ cmd.getOptionValue(VERBOSE[SHORT]));
405 				}
406 				catch(IllegalArgumentException ae) {
407 					throw new Exception(ae.getMessage());
408 				}
409 		
410 			}
411 			
412 			//Grab the targets specified implicitly
413 			if (cmd.getArgList().size() != 0)
414 				targetList.addAll(cmd.getArgList());
415 			//Grab the targets specified explicitly
416 			if (cmd.hasOption(TARGET[SHORT])) {
417 				targetList.addAll(
418 						Arrays.asList(cmd.getOptionValues(TARGET[SHORT])));
419 			}
420 			setTargets(targetList);
421 			
422 			// Check to see if aliasing is turned on
423 			if (cmd.hasOption(ALIAS[SHORT]))
424 				setAlias(true);
425 			// Check for the flag that indicates whether to follow pointers
426 			if (cmd.hasOption(FOLLOW[SHORT]))
427 				setFollowPtrs(false);
428 			// Check for the include paths to indicate the paths to search for 
429 			// when following pointers
430 			if (cmd.hasOption(INCLUDES[SHORT]))
431 				setIncludePaths(
432 						Arrays.asList(cmd.getOptionValues(INCLUDES[SHORT])));
433 			// TODO: Check later when data object validation is implemented
434 			// Check to see if data object validation is set
435 //			if (cmd.hasOption("O")) 
436 //				setDataObj(false);
437 			if (cmd.hasOption(PROGRESS[SHORT]))
438 				setProgress(true);
439 			// Check to see if VTool will not recurse down a directory tree
440 			if (cmd.hasOption(LOCAL[SHORT]))
441 				setRecursive(false);
442 			// Check to see if regular expressions were set
443 			if (cmd.hasOption(REGEXP[SHORT]))
444 				setRegexp(Arrays.asList(cmd.getOptionValues(REGEXP[SHORT])));
445 			// Check to get file that contains file patterns to ignore
446 			if (cmd.hasOption(IGNOREFILE[SHORT])) {
447 				setNoFiles(Arrays.asList(
448 						cmd.getOptionValues(IGNOREFILE[SHORT])));
449 			}
450 			// Check to get file that contains directory patterns to ignore
451 			if (cmd.hasOption(IGNOREDIR[SHORT])) {
452 				setNoDirs(Arrays.asList(cmd.getOptionValues(IGNOREDIR[SHORT])));
453 			}
454 			// Check to get the dictionary file(s) 
455 			if (cmd.hasOption(DICT[SHORT]))
456 				setDictionaries(Arrays.asList(cmd.getOptionValues(DICT[SHORT])));
457 			// Check to see what type of reporting style the report will have
458 			if (cmd.hasOption(RPTSTYLE[SHORT]))				
459 				setRptStyle(cmd.getOptionValue(RPTSTYLE[SHORT]));
460 			// Check to see where the human-readable report will go
461 			if (cmd.hasOption(REPORT[SHORT]))
462 				setRptFile(new File(cmd.getOptionValue(REPORT[SHORT])));
463 			// Check to see if standalone label fragment validation is enabled
464 			if (cmd.hasOption(PARTIAL[SHORT]))
465 				setForcePartial(true);
466 
467 		}
468 		catch(MissingOptionException moe) {
469 			System.err.println(moe.getMessage());
470 			System.exit(TOOLFAILURE);
471 		}
472 		catch(UnrecognizedOptionException uoe) {
473 			System.err.println(uoe.getMessage());
474 			System.exit(TOOLFAILURE);
475 		}
476 		catch(Exception e) {
477 			System.err.println(e.getMessage());
478 			System.exit(TOOLFAILURE);
479 		}
480 	}
481 	
482 	/***
483 	 * Get aliasing flag.
484 	 * @return 'true' if aliasing is ON, 'false' otherwise
485 	 */
486 	public boolean getAlias() {
487 		return this.alias;
488 	}
489 	
490 	/***
491 	 * Set aliasing flag.
492 	 * @param a 'false' if aliasing should be turned off, 
493 	 * 'true' otherwise
494 	 */
495 	public void setAlias(boolean a) {
496 		this.alias = a;
497 	}
498 	
499 	/***
500 	 * Get data object validation flag.
501 	 */
502 /*
503 	public boolean getDataObj() {
504 		return this.dataObj;
505 	}
506 */	
507 	/***
508 	 * Set data object flag.
509 	 * @param d 'true' if data object validation is to be performed, 
510 	 * 'false' otherwise
511 	 */
512 /*
513 	public void setDataObj(boolean d) {
514 		this.dataObj = d;
515 	}
516 */
517 	/***
518 	 * Get a list of dictionary files passed into VTool.
519 	 * @return a List object of dictionary file names
520 	 */
521 	public List getDictionaries() {
522 		return this.dictionaries;
523 	}
524 	
525 	/***
526 	 * Set the dictionary file names passed into VTool .
527 	 * @param d a List object of dictionary files
528 	 */
529 	public void setDictionaries(List d) {
530 		this.dictionaries = d;
531 	}
532 	
533 	/***
534 	 * Get flag status that determines whether to follow pointers found
535 	 * in a label.
536 	 * @return 'true' to follow, 'false' otherwise
537 	 */
538 	public boolean getFollowPtrs() {
539 		return this.followPtrs;
540 	}
541 	
542 	/***
543 	 * Set the flag that determines whether to follow pointers found in
544 	 * a label.
545 	 * @param f 'true' to follow, 'false' otherwise
546 	 */
547 	public void setFollowPtrs(boolean f) {
548 		this.followPtrs = f;
549 	}
550 	
551 	/***
552 	 * Get flag status that determines whether standalone label fragment
553 	 * validation is enabled.
554 	 * @return 'true' to enable, 'false' otherwise
555 	 */
556 	public boolean getForcePartial() {
557 		return this.force;
558 	}
559 	
560 	/***
561 	 * Set the flag that determines whether to validate standalone label.
562 	 * fragments
563 	 * @param f 'true' to enable, 'false' otherwise
564 	 */
565 	public void setForcePartial(boolean f) {
566 		this.force = f;
567 	}
568 	
569 	/***
570 	 * Get the paths to search for files referenced by pointers in a label.
571 	 * @return Start paths
572 	 */
573 	public List getIncludePaths() {
574 		return this.includePaths;
575 	}
576 	
577 	/***
578 	 * Set the paths to search for files referenced by pointers. 
579 	 * <p>
580 	 * Default is to always look first in the same directory
581 	 * as the label, then search specified directories.
582 	 * @param i List of paths
583 	 */
584 	public void setIncludePaths(List i) {
585 		this.includePaths = i;
586 	}
587 	
588 	/***
589 	 * Get the file name that contains the list of directories and/or
590 	 * directory patterns to ignore during validation.
591 	 * @return a list of directory names/patterns to exclude from validation
592 	 */
593 	public List getNoDirs() {
594 		return this.noDirs;
595 	}
596 	
597 	/***
598 	 * Set the flag to ignore specified directories.
599 	 * @param f a text file containing a list of directories and/or directory
600 	 *  patterns to ignore during validation. The names must be listed one
601 	 *  name per line.
602 	 */
603 	public void setNoDirs(List f) {
604 		this.noDirs = f;
605 	}
606 	
607 	/***
608 	 * Get the file name that contains the list of files and/or file patterns.
609 	 * to ignore during validation
610 	 * @return a list of files/file patterns to exclude from validation
611 	 */
612 	public List getNoFiles() {
613 		return this.noFiles;
614 	}
615 	
616 	/***
617 	 * Set the flag to ignore specified files.
618 	 * @param f a list of files/file patterns to ignore during validation.
619 	 */
620 	public void setNoFiles(List f) {
621 		this.noFiles = f;
622 	}
623 	
624 	/***
625 	 * Get the machine-readable log file name.
626 	 * @return log file
627 	 */
628 	public File getLogFile() {
629 		return this.logFile;
630 	}
631 	
632 	/***
633 	 * Set the file name for the machine-readable log.
634 	 * @param f file name of the log
635 	 */
636 	public void setLogFile(File f) {
637 		this.logFile = f;
638 	}
639 	
640 	/***
641 	 * Get the flag that determines whether to write the log to standard out.
642 	 * @return 'true' to show log, 'false' to not show log
643 	 */
644 	public boolean getShowLog() {
645 		return this.showLog;
646 	}
647 	
648 	/***
649 	 * Set the flag to write the log to standard out.
650 	 * @param l 
651 	 */
652 	public void setShowLog(boolean l) {
653 		this.showLog = l;
654 	}
655 	
656 	/***
657 	 * Get the report file name for the human-readable report.
658 	 * @return Report file name
659 	 */
660 	public File getRptFile() {
661 		return this.rptFile;
662 	}
663 	
664 	/***
665 	 * Set the file for the human-readable report.
666 	 * @param f file name
667 	 */
668 	public void setRptFile(File f) {
669 		this.rptFile = f;
670 	}
671 	
672 	/***
673 	 * Get the output style that was set for the validation report.
674 	 * @return 'full' for a full report, 'sum' for a summary report or
675 	 *  'min' for minimal detail
676 	 */
677 	public String getRptStyle() {
678 		return this.rptStyle;
679 	}
680 	
681 	/***
682 	 * Set the output style for the report.
683 	 * @param style 'sum' for a summary report, 'min' for a minimal report,
684 	 *  and 'full' for a full report
685 	 */
686 	public void setRptStyle(String style) {
687 		if ( (style.equalsIgnoreCase("sum") == false) &&
688 				(style.equalsIgnoreCase("min") == false) &&
689 				(style.equalsIgnoreCase("full") == false) ) {
690 				throw new IllegalArgumentException(
691 						"Invalid value entered for 's' flag. Value can only "
692 						+ "be either 'full', 'sum', or 'min'");
693 		}
694 		this.rptStyle = style;
695 	}
696 	
697 	/***
698 	 * Get the progress reporting flag.
699 	 * @return 'true' if progress reporting is enabled, 'false' otherwise
700 	 */
701 	
702 	public boolean getProgress() {
703 		return this.progress;
704 	}
705 	
706 	/***
707 	 * Set the progress reporting flag.
708 	 * @param p
709 	 */
710 	public void setProgress(boolean p) {
711 		this.progress = p;
712 	}
713 	
714 	/***
715 	 * Get the patterns to be matched when searching for files to validate in
716 	 * a directory.
717 	 * @return a List object of patterns
718 	 */
719 	public List getRegexp() {
720 		return this.regexp;
721 	}
722 	
723 	/***
724 	 * Set the patterns flag.
725 	 * @param e a List of patterns to be matched when searching for files to
726 	 *  validate in a directory
727 	 */
728 	public void setRegexp(List e) {
729 		this.regexp = e;
730 	}
731 	
732 	/***
733 	 * Set the recursive flag.
734 	 * @param r 'true' to recursively traverse down a directory and all its
735 	 *  sub-directories, 'false' otherwise
736 	 */
737 	public void setRecursive(boolean r) {
738 		this.recursive = r;
739 	}
740 	
741 	/***
742 	 * Get the list of targets
743 	 * @return a List object of files, URLs, and/or directories
744 	 */
745 	public List getTargets() {
746 		return this.targets;
747 	}
748 	
749 	/***
750 	 * Set the targets flag.
751 	 * @param t a List of files, URLs, and/or directories to be validated
752 	 */
753 	public void setTargets(List t) {
754 		this.targets = t;
755 	}
756 	
757 	/***
758 	 * Get the verbosity level.
759 	 * @return an integer value where '1' for info, '2' for warnings'
760 	 * and '3' for errors
761 	 */
762 	public short getVerbose() {
763 		return this.verbose;
764 	}
765 	
766 	/***
767 	 * Set the verbosity level and above to include in the reporting.
768 	 * @param v '1' for info, '2' for warnings, and '3' for errors
769 	 */
770 	public void setVerbose(short v) {
771 		if (v < 1 || v > 3) {
772 			throw new IllegalArgumentException(
773 					"Invalid value entered for 'v' flag. "
774 					+ "Valid values can only be 1, 2, or 3");
775 		}
776 		verbose = v;
777 
778 		if (verbose == 1)
779 			severity = new String("Info");
780 		else if (verbose == 2)
781 			severity = new String("Warning");
782 		else if (verbose == 3)
783 			severity = new String("Error");
784 	}
785 	
786 	/***
787 	 * Reads a configuration file to set the default behaviors for VTool.
788 	 * <p>
789 	 * Flags set on the command-line will override flags set in the 
790 	 * configuration file
791 	 * 
792 	 * @param file a file containing keyword/value statements
793 	 */
794 	public void readConfigFile(URL file) {
795 		Configuration config = null;
796 		AbstractConfiguration.setDelimiter(' ');
797 		
798 		try {	
799 			config = new PropertiesConfiguration(file);
800 		}
801 		catch (ConfigurationException ce) {
802 			System.err.println(ce.getMessage());
803 			System.exit(TOOLFAILURE);
804 		}
805 		
806 		try {
807 			if (config.isEmpty())
808 				throw new Exception("Configuration file is empty: "
809 						+ file.toString());
810 			if (config.containsKey(ALIASKEY))
811 				setAlias(config.getBoolean(ALIASKEY));
812 			//TODO: Get Data Object key value when data object validation is implemented
813 //			if (config.containsKey(DATAOBJKEY))
814 //				setDataObj(config.getBoolean(DATAOBJKEY));
815 			if (config.containsKey(DICTKEY))
816 				setDictionaries(config.getList(DICTKEY));
817 			if (config.containsKey(FORCEKEY))
818 				setForcePartial(config.getBoolean(FORCEKEY));
819 			if (config.containsKey(FOLLOWKEY))
820 				setFollowPtrs(config.getBoolean(FOLLOWKEY));
821 			if (config.containsKey(IGNOREDIRKEY)) {
822 				setNoDirs(config.getList(IGNOREDIRKEY));
823 				// Removes quotes surrounding each pattern being specified
824 				for(int i=0; i < this.noDirs.size(); i++) {
825 					this.noDirs.set(i, 
826 							this.noDirs.get(i).toString().replace('"', ' ').trim());
827 				}
828 			}
829 			if (config.containsKey(IGNOREFILEKEY)) {
830 				setNoFiles(config.getList(IGNOREFILEKEY));
831 				// Removes quotes surrounding each pattern being specified
832 				for(int i=0; i < noFiles.size(); i++) {
833 					this.noFiles.set(i, 
834 							this.noFiles.get(i).toString().replace('"', ' ').trim());
835 				}
836 			}
837 			if (config.containsKey(INCLUDESKEY))
838 				setIncludePaths(config.getList(INCLUDESKEY));
839 			if (config.containsKey(LOGKEY)) {
840 				String logValue = new String(config.getProperty(LOGKEY).toString());
841 				// Check for a boolean value or a file name
842 				if(logValue.equalsIgnoreCase("true") 
843 						|| logValue.equalsIgnoreCase("false")) {
844 					setShowLog(Boolean.valueOf(logValue).booleanValue());
845 				}
846 				else
847 					setLogFile(new File(logValue));
848 			}
849 			if (config.containsKey(PROGRESSKEY))
850 				setProgress(config.getBoolean(PROGRESSKEY));
851 			if (config.containsKey(RECURSEKEY))
852 				setRecursive(config.getBoolean(RECURSEKEY));
853 			if (config.containsKey(REGEXPKEY)) {
854 				setRegexp(config.getList(REGEXPKEY));
855 				// Removes quotes surrounding each pattern being specified
856 				for(int i=0; i < this.regexp.size(); i++) {
857 					this.regexp.set(i, 
858 							this.regexp.get(i).toString().replace('"', ' ').trim());
859 				}
860 			}
861 			if (config.containsKey(REPORTKEY))
862 				setRptFile(new File(config.getString(REPORTKEY)));
863 			if (config.containsKey(STYLEKEY))
864 				setRptStyle(config.getString(STYLEKEY));
865 			if (config.containsKey(TARGETKEY))
866 				setTargets(config.getList(TARGETKEY));
867 			if (config.containsKey(VERBOSEKEY))
868 				setVerbose(config.getShort(VERBOSEKEY));
869 		} catch(ConversionException ce) {
870 			System.err.println(ce.getMessage());
871 			System.exit(TOOLFAILURE);
872 		} catch(Exception ex) {
873 			System.err.println(ex.getMessage());
874 			System.exit(TOOLFAILURE);
875 		}
876 	}
877 	
878 	/***
879 	 * Logs report header information such as version of the tool, 
880 	 * execution time, and flag settings.
881 	 *
882 	 */
883 	public void logRptHeader() {
884 		ToolsTime time = new ToolsTime();
885 
886 		//TODO: Print out data object validation
887 		log.log(new ToolsLogRecord(Level.CONFIG, 
888 				"VTool Version             " + VERSION_ID));
889 		try {
890 			log.log(new ToolsLogRecord(Level.CONFIG, 
891 				"Execution Date            " 
892 				+ time.getTime(new SimpleDateFormat("EEE, MMM dd yyyy 'at' HH:mm:ss a"))));
893 		} catch(IllegalArgumentException eX) {
894 			System.out.println(eX.getMessage());
895 			System.exit(TOOLFAILURE);
896 		}
897 		
898 		log.log(new ToolsLogRecord(ToolsLevel.PARAMETER,
899 				"Target(s)                      " + targets));
900 		if (dictionaries != null) {
901 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
902 				"Dictionary File(s)             " + dictionaries));
903 		}
904 		
905 		log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
906 				"Aliasing                       " + alias));
907 		log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
908 				"Directory Recursion            " + recursive));
909 		log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
910 				"Follow Pointers                " + followPtrs));
911 		log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
912 				"Validate Standalone Fragments  " + force));
913 		
914 		if (logFile != null) {
915 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
916 					"Log File                       " + logFile));
917 		}
918 		if (rptFile != null) {
919 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
920 					"Report File                    " + rptFile));
921 		}
922 		if (rptStyle != null) {
923 		    log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
924 		    		"Report Style                   " + rptStyle));
925 		}
926 		if (severity != null) {
927 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
928 					"Severity Level                 " + severity));
929 		}
930 		if (includePaths != null) {
931 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
932 					"Include Path(s)                " + includePaths));
933 		}
934 		if (config != null) {
935 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
936 					"Configuration File             " + config));
937 		}
938 		if (regexp != null) {
939 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
940 					"Files Patterns                 " + regexp));
941 		}
942 		if (noDirs != null) {
943 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
944 					"Excluded Directories           " + noDirs));
945 		}
946 		if (noFiles != null) {
947 			log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
948 					"Excluded Files                 " + noFiles));
949 		}
950 		log.log(new ToolsLogRecord(ToolsLevel.PARAMETER, 
951 				"Progress Reporting             " + progress));
952 	}
953 	
954 	/***
955 	 * Configures the logger appropriately.
956 	 * <p>
957 	 * If a log file was specified on the command-line, the log
958 	 * will be written to that file. If the log flag was
959 	 * specified with no file spec, then the log will be written
960 	 * to standard out. Otherwise, the log will be written to
961 	 * memory (ByteArrayOutputStream). 
962 	 *
963 	 */
964 	public void setupLogger() {
965 		Logger logger = Logger.getLogger("");
966 		logger.setLevel(Level.ALL);
967 		
968 		Handler []handler = logger.getHandlers();
969 		for(int i = 0; i < logger.getHandlers().length; i++) 
970 			logger.removeHandler(handler[i]);
971 
972 		
973 		//Write to a file if a log file was specified
974 		if(logFile != null) {
975 			FileHandler file = setupFileHandler(logFile);
976 			logger.addHandler(file);
977 			logHandlers.add(file);
978 		}
979 		//Write to standard out if the log option flag was specified
980 		else if (showLog == true && rptFile == null) {
981 			StreamHandler stream = setupStreamHandler(System.out);
982 			logger.addHandler(stream);
983 			logHandlers.add(stream);
984 		}
985 		//Write to both standard out and memory if the log option flag
986 		//was specified and no report file was specified
987 		else if (showLog == true && rptFile != null) {
988 			byteStream = new ByteArrayOutputStream();
989 			StreamHandler byteArray = setupStreamHandler(byteStream);
990 			logger.addHandler(byteArray);
991 			logHandlers.add(byteArray);
992 			
993 			StreamHandler stream = setupStreamHandler(System.out);
994 			logger.addHandler(stream);
995 			logHandlers.add(stream);
996 		}
997 		//Write to memory if none of the above conditions exist
998 		else {
999 			byteStream = new ByteArrayOutputStream();
1000 			StreamHandler byteArray = setupStreamHandler(byteStream);
1001 			logger.addHandler(byteArray);
1002 			logHandlers.add(byteArray);
1003 		}
1004 			
1005 	}
1006 	
1007 	/***
1008 	 * Sets up a handler to an outputstream.
1009 	 * @param out An outputstream to write the logger to
1010 	 * @return a stream handler
1011 	 */
1012 	private StreamHandler setupStreamHandler(OutputStream out) {
1013 		StreamHandler stream = new StreamHandler(out, new ToolsLogFormatter());
1014 		stream.setLevel(Level.ALL);
1015 		
1016 		return stream;
1017 	}
1018 	
1019 	/***
1020 	 * Sets up a file handler.
1021 	 * @param log The name of the file to write the logger to
1022 	 * @return a file handler to the input file
1023 	 */
1024 	private FileHandler setupFileHandler(File log) {
1025 		FileHandler file = null;
1026 		try {
1027 			file = new FileHandler(log.toString(), false);
1028 			file.setLevel(Level.ALL);
1029 			file.setFormatter(new ToolsLogFormatter());
1030 		} catch (SecurityException s) {
1031 			System.err.println(s.getMessage());
1032 			System.exit(TOOLFAILURE);
1033 		} catch (IOException iEx) {
1034 			System.err.println(iEx.getMessage());
1035 			System.exit(TOOLFAILURE);
1036 		}
1037 		return file;
1038 	}
1039 	
1040 	/***
1041 	 * Convert a string to a URL.
1042 	 * @param s The string to convert
1043 	 * @return A URL of the input string
1044 	 */
1045 	private URL toURL(String s) {
1046 		URL url = null;		
1047 		try {
1048 			url = new URL(s);
1049 		} catch (MalformedURLException ex) {
1050 			try {
1051 				url = new File(s).toURI().toURL();
1052 			} catch (MalformedURLException mEx) {
1053 				System.err.println(mEx.getMessage());
1054 				System.exit(TOOLFAILURE);
1055 			}
1056 		}
1057 		return url;
1058 	}
1059 	
1060 	/***
1061 	 * Parse the dictionary files.
1062 	 * 
1063 	 * @param dictionary a list of dictionary URLs
1064 	 * @return a Dictionary object that includes all the dictionary
1065 	 *  information from all the dictionary files passed in.
1066 	 */
1067 	public Dictionary readDictionaries(List dictionary) {
1068 		Dictionary dict = null;
1069 		URL dd = null;
1070 		Iterator i = dictionary.iterator();
1071 		
1072 		try {
1073 			//Parse the first dictionary
1074 			dd = toURL(i.next().toString());
1075 			dict = DictionaryParser.parse(dd, this.alias);
1076             log.log(new ToolsLogRecord(Level.CONFIG, 
1077             		"Dictionary version        \n"
1078             		+ dict.getInformation(), dd.toString()));
1079             //Log dictionary status only upon a FAIL
1080             if(dict.getStatus().equals(FAIL)) {
1081             	log.log(new ToolsLogRecord(ToolsLevel.NOTIFICATION, 
1082 					dict.getStatus(), dd.toString()));
1083             }
1084 			//Parse the rest of the dictionaries
1085 			while (i.hasNext()) {
1086 				dd = toURL(i.next().toString());
1087 				Dictionary mergeDict = DictionaryParser.parse(dd, alias);
1088 				dict.merge( mergeDict, true );
1089 				log.log(new ToolsLogRecord(Level.CONFIG, 
1090 				    "Dictionary version        \n"
1091 					+ mergeDict.getInformation(), dd.toString()));
1092 				//Log dictionary status only upon a FAIL
1093 				if(mergeDict.getStatus().equals(FAIL)) {
1094 					log.log(new ToolsLogRecord(ToolsLevel.NOTIFICATION, 
1095 						mergeDict.getStatus(), dd.toString()));
1096 				}
1097 			}
1098 		} catch (MalformedURLException uex) {
1099 			System.err.println(uex.getMessage());
1100 			System.exit(TOOLFAILURE);
1101 		} catch (IOException iex) {
1102 			System.err.println(iex.getMessage());
1103 			System.exit(TOOLFAILURE);
1104 		} catch (gov.nasa.pds.tools.label.parser.ParseException pe) {
1105 			log.log(new ToolsLogRecord(ToolsLevel.NOTIFICATION, 
1106 					"FAIL", dd.toString()));
1107 			dictPassed = false;
1108 		}
1109 		return dict;	
1110 	}
1111 	
1112 	/***
1113 	 * Sets up the include paths and flag to follow pointers in
1114 	 * the parser.
1115 	 * @param parser The label parser to set properties for
1116 	 */
1117 	private void setParserProps(LabelParser parser) {
1118 		URL path = null;
1119 		if (includePaths != null) {
1120 			for( Iterator i = includePaths.iterator(); i.hasNext(); ) {
1121 				path = toURL(i.next().toString());
1122 				checkURL(path);
1123 				parser.addIncludePath(path);
1124 			}
1125 		}
1126 
1127 		if (followPtrs == false)
1128 			parser.getProperties().setProperty("parser.pointers", "false");
1129 	}
1130 	
1131 	/***
1132 	 * Checks for the existence of a URL.
1133 	 * @param url the url to check
1134 	 */
1135 	private void checkURL(URL url) {
1136 		try {
1137 			url.openStream();
1138 		} catch (IOException i) {
1139 			System.err.println(i.getMessage());
1140 			System.exit(TOOLFAILURE);
1141 		}
1142 	}
1143 	
1144 	/***
1145 	 * Validate labels found in the specified targets. If the target
1146 	 * is a directory, this method will validate all labels found within
1147 	 * the directory and its sub-directories. If recursion is turned OFF,
1148 	 * then sub-directories will not be looked into.
1149 	 * 
1150 	 * @param targets a list of files, directories, and/or URLs
1151 	 * @param dict the dictionary file
1152 	 */	
1153 	public void validateLabels(List targets, Dictionary dict) {
1154 		
1155 		for (Iterator i1 = targets.iterator(); i1.hasNext();) {
1156 			FileList fileList = new FileList();
1157 			fileList = processTarget(i1.next().toString(), recursive);
1158 			
1159 			for (Iterator i2 = fileList.getFiles().iterator(); i2.hasNext();) {
1160 				URL target = toURL(i2.next().toString());				
1161 				String result = validateLabel(target, dict);
1162 				saveResults(result);
1163 			}
1164 			
1165 			if (!fileList.getDirs().isEmpty())
1166 				validateLabels(fileList.getDirs(), dict);
1167 		}
1168 		
1169 	}
1170 	
1171 	/***
1172 	 * Record the results of a label validation to determine the success
1173 	 * of the tool run.
1174 	 * @param result 'PASS' for a good label, 'FAIL' for a bad label, or
1175 	 * 'UNKNOWN' for a label that skipped validation
1176 	 */
1177 	private void saveResults(String result) {
1178 		if (PASS.equals(result))
1179 			++goodLbls;
1180 		else if (FAIL.equals(result))
1181 			++badLbls;
1182 		else
1183 			++unkLbls;
1184 	}
1185 	
1186 	/***
1187 	 * Returns an exit status code based on the validation results.
1188 	 * 
1189 	 * @return '0' if files "passed" and a '1' if any of the following
1190 	 * occurs:<br>
1191 	 * <ul>
1192 	 * <li>one or more files failed validation</li>
1193 	 * <li>one or more files skipped validation and in addition,  
1194 	 *   no other files passed validation</li>
1195 	 * <li>dictionary validation failed</li>
1196 	 * </ul>
1197 	 */
1198 	public int getExitStatus() {
1199 		int status = 0;
1200 		
1201 		//Bad run if there were one or more bad labels
1202 		if (badLbls > 0)
1203 			status = BADRUN;
1204 		//Bad run if there were one or more files that 
1205 		//skipped validation and in addition, no other
1206 		//files passed
1207 		else if (unkLbls > 0 && goodLbls == 0)
1208 			status = BADRUN;
1209 		//Bad run if there was a dictionary error
1210 		else if (dictPassed == false)
1211 			status = BADRUN;
1212 		else
1213 			status = GOODRUN;
1214 		
1215 		return status;
1216 	}
1217 	
1218 	/***
1219 	 * Processes a target.
1220 	 * 
1221 	 * @param target The file or URL to process
1222 	 * @param getSubDirs 'True' to look for sub-directories, 'false' otherwise
1223 	 * @return A FileList object containing the sub-directories and files
1224 	 *  found in the target, if any
1225 	 */
1226 	public FileList processTarget(String target, boolean getSubDirs) {		
1227 		FileListGenerator fileGen = new FileListGenerator();
1228 		FileList list = new FileList();
1229 		fileGen.setFilters(regexp, noFiles, noDirs);
1230 		
1231 		try {
1232 			list = fileGen.visitTarget(target, getSubDirs);
1233 		} catch (MalformedURLException uEx) {
1234 			System.err.println(uEx.getMessage());
1235 			System.exit(TOOLFAILURE);
1236 		} catch (IOException iEx) {
1237 			System.err.println(iEx.getMessage());
1238 			System.exit(TOOLFAILURE);
1239 		} catch (BadLocationException bEx) {
1240 			System.err.println(bEx.getMessage());
1241 			System.exit(TOOLFAILURE);
1242 		} catch (NullPointerException nEx) {
1243 			System.err.println(nEx.getMessage());
1244 			System.exit(TOOLFAILURE);
1245 		}
1246 		return list;
1247 	}
1248 	
1249 	/***
1250 	 * Validate a label file.
1251 	 * 
1252 	 * @param file The URL of the file to be validated
1253 	 * @param dict a Dictionary object needed for semantic validation. If null,
1254 	 *             only syntactic validation will be performed.
1255 	 * @return 'PASS' if the label passed the PDS validation step, 
1256 	 *         'FAIL' if the label failed the PDS validation step, or
1257 	 *         'UNKOWN' if the label skipped the PDS validation step
1258 	 */
1259 	
1260 	public String validateLabel(URL file, Dictionary dict) {		
1261 		LabelParserFactory factory = LabelParserFactory.getInstance();
1262 		LabelParser parser = factory.newLabelParser();
1263 		Label label = null;
1264 		setParserProps(parser);
1265 		
1266 		if (progress)
1267 			showProgress(file);
1268 		try {
1269 			//Check to see if the file is a fragment and if standalone fragment
1270 			//validation was enabled
1271 			if ( (FilenameUtils.getExtension(file.getFile()).equalsIgnoreCase(FRAG_EXT))
1272 				&& (force) ) {
1273 				if (dict == null)
1274 					label = parser.parsePartial(file);
1275 				else
1276 					label = parser.parsePartial(file, dict);
1277 			}
1278 			else {
1279 				if (dict == null)
1280 					label = parser.parse(file);
1281 				else
1282 					label = parser.parse(file, dict);
1283 			}
1284 		} catch (gov.nasa.pds.tools.label.parser.ParseException pEx) {
1285 			log.log(new ToolsLogRecord(ToolsLevel.NOTIFICATION, 
1286 					"SKIP", file.toString()));
1287 			return UNKNOWN;
1288 		} catch (IOException iEx) {
1289 			log.log(new ToolsLogRecord(ToolsLevel.NOTIFICATION, 
1290 					"SKIP", file.toString()));
1291 			log.log(new ToolsLogRecord(ToolsLevel.WARNING, 
1292 					iEx.getMessage(), file.toString()));
1293 			return UNKNOWN;			
1294 		}
1295 		log.log(new ToolsLogRecord(ToolsLevel.NOTIFICATION, 
1296 				label.getStatus(), file.toString()));
1297 		
1298 		return label.getStatus();
1299 	}
1300 	
1301 	/***
1302 	 *  Prints out the current directory being validated and
1303 	 *  represents each file being validated by an asterisk.
1304 	 *  This is used when progress reporting is enabled.
1305 	 * 
1306 	 * @param file The URL being validated
1307 	 */
1308 	public void showProgress(URL file) {
1309 		URL dir = null;
1310 		try {
1311 			dir = new URL(file.toString().
1312 					substring(0, file.toString().lastIndexOf("/")));
1313 		} catch (MalformedURLException uEx) {
1314 			System.err.println(uEx.getMessage());
1315 			System.exit(TOOLFAILURE);
1316 		}
1317 		
1318 		if (dir.toString().equals(currDir)) {
1319 			System.err.print(FILE_REP);
1320 		}
1321 		else {
1322 			currDir = new String(dir.toString());
1323 			System.err.println("\nValidating file(s) in: " + currDir);
1324 			System.err.print(FILE_REP);
1325 		}
1326 	}
1327 	
1328 	/***
1329 	 * Transforms an XML log to a human-readable report.
1330 	 * 
1331 	 * @param in inputstream of the XML log
1332 	 * @param xsl the stylesheet to use in creating the human-readable report
1333 	 * @param level the severity level to set for the report (info, warning, or error)
1334 	 * @param output The stream type for the final report
1335 	 * @throws TransformerException If there was an error during the transformation
1336 	 *  of the log to the final report
1337 	 */
1338     private void generateReport(InputStream in, String xsl, String level, 
1339     							OutputStream output) throws TransformerException {
1340 
1341         Source xmlSource = new StreamSource(in);
1342         Source xsltSource = new StreamSource(getClass().getResourceAsStream(xsl));
1343         Result result = new StreamResult(output);
1344         TransformerFactory factory = TransformerFactory.newInstance();
1345         Transformer transformer = factory.newTransformer(xsltSource);
1346         
1347         transformer.setParameter("level", level);
1348         transformer.transform(xmlSource, result);
1349     }
1350 	
1351 	/***
1352 	 * Creates a human-readable report.
1353 	 * 
1354 	 * @param log Where the xml log is located. If null, then the xml log
1355 	 *  will be read from memory.
1356 	 * @param report Where the human-readable report will be written to.
1357 	 *  If null, then it goes to standard out.
1358 	 * @param level The severity level to include in the report. Can be
1359 	 *  "INFO", "WARNING", or "ERROR".
1360 	 * @param style The reporting style to generate. Can be either "full",
1361 	 *  "sum", or "min" for a full, summary, or minimal report, respectively.
1362 	 */
1363 	public void doReporting(File log, File report, String level, String style) {
1364 		String xsl = null;
1365 		InputStream input = null;
1366 		OutputStream output = null;
1367 		
1368 		if ( (level.equalsIgnoreCase("info") == false) 
1369 			  && (level.equalsIgnoreCase("warning") == false)
1370 			  && (level.equalsIgnoreCase("error") == false) ) {
1371 				throw new IllegalArgumentException("Invalid severity level: "
1372 						+ level + ". Must be 'info', 'warning', or 'error'.");
1373 		}
1374 		
1375 		xsl = getXSL(style);
1376 
1377 		try {
1378 			input = getRptInStream(log);
1379 			output = getRptOutStream(report);
1380 		} catch (FileNotFoundException ex) {
1381 			System.err.println(ex.getMessage());
1382 			System.exit(TOOLFAILURE);
1383 		}
1384 		
1385 		try {
1386 			generateReport(input, xsl, level.toUpperCase(), output);
1387 		} catch (TransformerException t) {
1388 			System.err.println(t.getMessage());
1389 			System.exit(TOOLFAILURE);
1390 		}
1391 		//If report is written to standard out, add an extra line terminator
1392 		if(report == null)
1393 			System.out.println();
1394 	}
1395 	
1396 	/***
1397 	 * Gets the proper XSL stylesheet.
1398 	 * 
1399 	 * @param style 'full' for a full XSL, 'sum' for a summary XSL, or 'min'
1400 	 *  for a minimal XSL 
1401 	 * @return The XSL stylesheet name
1402 	 */
1403 	private String getXSL(String style) {
1404 		if (style.equalsIgnoreCase("full"))
1405 			return FULLXSL;
1406 		else if (style.equalsIgnoreCase("sum"))
1407 			return SUMXSL;
1408 		else if (style.equalsIgnoreCase("min"))
1409 			return MINXSL;
1410 		else {
1411 			throw new IllegalArgumentException("Invalid style specified: "
1412 				+ style + ". Must be 'full', 'sum' or 'min'");
1413 		}
1414 	}
1415 	
1416 	/***
1417 	 * Returns the input stream of the XML log for the final report.
1418 	 * 
1419 	 * @param file A file name. If null, then a ByteArrayInputStream is returned. 
1420 	 * 
1421 	 * @return The proper input stream 
1422 	 * @throws FileNotFoundException 
1423 	 */
1424 	private InputStream getRptInStream(File file) throws FileNotFoundException {	
1425 		if(file != null)
1426 				return new FileInputStream(file);
1427 		else
1428 			return new ByteArrayInputStream(byteStream.toByteArray());
1429 	}
1430 	
1431 	/***
1432 	 * Returns the appropriate output stream for the final report.
1433 	 * @param rpt The report file name. If null, then the standard out
1434 	 *  stream is returned.
1435 	 * 
1436 	 * @return The proper output stream
1437 	 * @throws FileNotFoundException
1438 	 */
1439 	private OutputStream getRptOutStream(File rpt) throws FileNotFoundException {
1440 		if(rpt != null) 
1441 			return new FileOutputStream(rpt);
1442 		else
1443 			return System.out;
1444 	}
1445 	  
1446     /***
1447      * Closes the handlers that were set for the logger.
1448      *
1449      */
1450 	public void closehandle() {
1451 		for(Iterator i=logHandlers.iterator(); i.hasNext();) {
1452 			Handler handler = (Handler) i.next();
1453 			handler.close();
1454 		}
1455 	}
1456 	/***
1457 	 * Implementation to perform automated PDS validation.
1458 	 * 
1459 	 * <p>
1460 	 * The main calls the following methods (in this order):
1461 	 * <p>
1462 	 * To setup the flags and parse the command-line options:
1463 	 * <ul> 
1464 	 * <li>buildOpts</li>
1465 	 * <li>parseLine</li>
1466 	 * <li>queryCmdLine</li>
1467 	 * </ul>
1468 	 * 
1469 	 * <p>
1470 	 * To setup the logger and log the report header information:
1471 	 * <ul>
1472 	 * <li>setupLogger</li>
1473 	 * <li>logRptHeader</li>
1474 	 * </ul>
1475 	 * 
1476 	 * <p>
1477 	 * To perform validation:
1478 	 * <ul>
1479 	 * <li>readDictionaries (if the PSDD was passed in)</li>
1480 	 * <li>validateLabels</li>
1481 	 * </ul>
1482 	 * 
1483 	 * <p>
1484 	 * To create the final report:
1485 	 * <ul>
1486 	 * <li>closehandle (to flush the buffers)</li>
1487 	 * <li>doReporting</li>
1488 	 * </ul>
1489 	 * 
1490 	 * <p>
1491 	 * Reporting is not generated if the log flag was specified with
1492 	 * no file spec and the report file flag was not specified
1493 	 * 
1494 	 * <p>
1495 	 * VTool returns an appropriate exit status based on validation results.
1496 	 * <br>
1497 	 * In general, the following is returned:
1498 	 * <ul>
1499 	 * <li>'0' upon "successful" validation</li>
1500 	 * <li>'1' upon a valiadation "failure"</li>
1501 	 * <li>'-1' upon application failure</li>
1502 	 * </ul>
1503 	 * 
1504 	 * @param argv Arguments passed on the command-line
1505 	 */
1506 	public static void main(String[] argv) {
1507 		VTool vtool = new VTool();
1508 		Dictionary dictionary = null;
1509 
1510 		if (argv.length == 0) {
1511 			System.out.println("\nType 'VTool -h' for usage");
1512 			System.exit(GOODRUN);
1513 		}
1514 		
1515 		// Define options
1516 		vtool.buildOpts();
1517 		// Parse the command line
1518 		vtool.parseLine(argv);
1519 		// Query the command line
1520 		vtool.queryCmdLine();
1521 		//Setup the logger
1522 		vtool.setupLogger();
1523 		// Log the report header
1524 		vtool.logRptHeader();	
1525 		
1526 		if (vtool.dictionaries != null) {
1527 			dictionary = vtool.readDictionaries(vtool.dictionaries);
1528 			
1529 			//Validate labels only if the dictionary passed
1530 			if (vtool.dictPassed)
1531 				vtool.validateLabels(vtool.targets, dictionary);
1532 		}
1533 		else
1534 			vtool.validateLabels(vtool.targets, null);
1535 		
1536 		vtool.closehandle();
1537 		
1538 		//Add extra line terminator if progress reporting was enabled
1539 		if(vtool.progress == true)
1540 			System.err.println();
1541 		
1542 		//If the log flag with no file spec was specified AND the
1543 		//report file flag is not specified, then do not create a report
1544 		if( !(vtool.showLog && vtool.rptFile == null) ) {
1545 			vtool.doReporting(vtool.logFile, vtool.rptFile, vtool.severity, vtool.rptStyle);
1546 		}
1547 		
1548 		System.exit(vtool.getExitStatus());
1549 	}
1550 }