Commit in lcsim/src/org/lcsim/job on MAIN
JobControlManager.java+523-3971.49 -> 1.50
add additional command-line options; disallow running multiple jobs with the same manager (create a new one instead); add some more comments

lcsim/src/org/lcsim/job
JobControlManager.java 1.49 -> 1.50
diff -u -r1.49 -r1.50
--- JobControlManager.java	31 Mar 2011 01:36:38 -0000	1.49
+++ JobControlManager.java	31 Mar 2011 23:19:28 -0000	1.50
@@ -27,6 +27,7 @@
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
@@ -40,6 +41,8 @@
 import org.jdom.Element;
 import org.jdom.Text;
 import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
 import org.lcsim.LCSimVersion;
 import org.lcsim.units.Constants;
 import org.lcsim.util.Driver;
@@ -62,56 +65,87 @@
  * substituted into the XML file.  An unlimited number of command-line variable 
  * definitions are allowed.  
  * 
- * In the XML file, variables have the format <code>${var}</code>.  No nested variable 
- * definitions are allowed (e.g. no variables within variables).  
+ * The <code>v</code> option turns on verbose mode.
+ * 
+ * In the XML file, variables have the format <code>${[varname]}</code>.  No nested variable 
+ * definitions are allowed (e.g. no variables within variables).
+ * 
+ * Command-line parameters that can be defined using switches are overridden by the
+ * corresponding settings in the job XML file, if they are present.  This means that
+ * if these parameters are to be taken from the CL, the matching settings should be
+ * left out of the XML job file.
+ * 
+ * A single <code>JobControlManager</code> should be used to execute one job, after
+ * which it should be discarded.  If the {@link #run()} method is called twice on 
+ * the same manager, a fatal exception will be thrown.
  *   
- * @version $Id: JobControlManager.java,v 1.49 2011/03/31 01:36:38 jeremy Exp $
+ * @version $Id: JobControlManager.java,v 1.50 2011/03/31 23:19:28 jeremy Exp $
  * @author Jeremy McCormick
  */
 public class JobControlManager
 {
-    private LCSimLoop loop;
+    // The LCIO record loop.
+    private LCSimLoop loop = new LCSimLoop();
     
-    private Map<String, Driver> driverMap;
-	private List<Driver> driverExec;	
+    // Driver management.
+    private Map<String, Driver> driverMap = new LinkedHashMap<String, Driver>();
+	private List<Driver> driverExec = new ArrayList<Driver>();	
 	private Map<String, String> availableDrivers = new HashMap<String, String>();
 	
-	private List<File> inputFiles;
-	private Map<String,String> variableMap;
-	private Map<String,Double> constants = new HashMap<String,Double>();
-	
+	// Run parameters.
+	private List<File> inputFiles = new ArrayList<File>();
 	private int maxEvents = -1;
 	private int skipEvents = -1;
+	private File rewriteFile;
 	
-	private File cacheDirectory;
-	private FileCache fileCache; // Start with default dir.
-	
-	private ClassLoader loader;
-	
+	// Variables and constants.
+	private Map<String, String> variableMap = new HashMap<String, String>();
+	private Map<String, Double> constantsMap = new HashMap<String, Double>();  
+		
 	// Boolean job options.
-	private boolean printInputFiles;
-	private boolean printDriverStatistics;
-	private boolean printSystemProperties;
-	private boolean printUserClassPath;
-	private boolean printDriversDetailed;
-	private boolean printVersion;
-	private boolean verbose;
-	private boolean wasSetup;
-	private boolean dryRun;
+    private boolean performDryRun;
+    private boolean rewrite;
+    
+    // Settings effecting logging verbosity.
+    private boolean printInputFiles;
+    private boolean printDriverStatistics;
+    private boolean printSystemProperties;
+    private boolean printUserClassPath;
+    private boolean printDriversDetailed;
+    private boolean printVersion;
+    private boolean verbose;
+    
+    // File caching.
+    private File cacheDirectory;
+    private FileCache fileCache; // Start with default dir.     
+   
+    // True once job parameters are setup from CL and XML.
+    private boolean wasSetup;
+    
+    // Flag so that only one job is run by the manager.
+    private boolean ranJob;
+        
+	// The manager's class loader.
+	private ClassLoader loader;	
 	
-	// The log stream.
-	private PrintStream logStream = System.out;
+	// The log stream for messages.
+	PrintStream logStream = System.out;
 	
-	private Element root;	
+	// Root node of XML job file.
+	private Element root; 
 	
-	private JDOMExpressionFactory factory;
+	// XML utils.
+	private JDOMExpressionFactory factory = new JDOMExpressionFactory();	
+	private ParameterConverters paramConverter = new ParameterConverters(factory);
 	
-	private static ParameterConverters paramConverter;
+	// Command line options.
+	private static final Options options = createCommandLineOptions();
 	
-	private Options options;
+	// Reg exp to extract variables from a string with the form "${varName}".
+	private static final Pattern varPattern = Pattern.compile("[$][{][a-zA-Z_-]*[}]");
 	
 	/**
-	 * This is the default no-argument constructor.
+	 * The default constructor.
 	 */
 	public JobControlManager()
 	{
@@ -122,10 +156,7 @@
 	    catch (IOException x)
 	    {
 	        throw new RuntimeException(x);
-	    }
-	    
-	    // Create Options object to represent command-line args.
-	    this.options = createCommandLineOptions();
+	    }	    
 	}
 	
 	/**
@@ -134,47 +165,65 @@
 	 */
 	public static void main(String args[])
 	{		
-	    // FIXME Manual usage printout bypassing CLI.
-	    if (args.length == 0)
-	    {
-	        System.out.println("Usage: -pPropFile.prop -Dvar=value steeringFile.xml");
-	        return;
-	    }
 		JobControlManager mgr = new JobControlManager();
+        if (args.length == 0)
+        {
+            mgr.logStream.println("java -jar lcsim-bin.jar [options] steeringFile.xml");
+            HelpFormatter help = new HelpFormatter();
+            help.printHelp(" ", options);
+            System.exit(1);
+        }
 		mgr.parseCommandLineOptions(args);
 		mgr.run();
 	}
-	
+		
 	/**
 	 * Create the command line options.
-	 * @return The command line options.
+	 * @return The command line options for the manager.
 	 */
-	private Options createCommandLineOptions()
+	private static Options createCommandLineOptions()
 	{
 	    Options options = new Options();
 	    
-	    Option propOpt = new Option("p", true, "Properties file with variable definitions.");
-	    Option defOpt = new Option("D", true, "Variable definition.");
+	    Option propOpt = new Option("p", true, "Load a properties file containing variable definitions");
+	    Option defOpt = new Option("D", true, "Define a variable with form [name]=[value]");
+	    Option rewriteOpt = new Option("w", true, "Rewrite the XML file with variables resolved");
+	    Option verbOpt = new Option("v", false, "Turn on verbose mode");
+	    Option skipOpt = new Option("s", true, "Set the number of events to skip.");
+	    Option evtOpt = new Option("n", true, "Set the max number of events to process.");
+	    Option dryOpt = new Option("x", false, "Perform a dry run which does not process events");
+	    Option quietOpt = new Option("q", false, "Turn on quiet mode.");
 	    
 	    options.addOption(propOpt);
 	    options.addOption(defOpt);
+	    options.addOption(rewriteOpt);
+	    options.addOption(verbOpt);
+	    options.addOption(skipOpt);
+	    options.addOption(evtOpt);
+	    options.addOption(dryOpt);
+	    options.addOption(quietOpt);
 
 	    return options;
 	}
 	
 	/**
-	 * Parse command-line options and setup internal state from them.  This method
-	 * calls {@link #setup(File)} to load the steering paramters from the XML file
-	 * after processing other command line options.
+	 * Parse command-line options and setup job state from them.  This method
+	 * calls {@link #setup(File)} to load the steering paramters from an XML file,
+	 * after processing other command line options.  This method is private so that
+	 * callers must all use the {@link #main(String[])} routine as the primary
+	 * entry point.
 	 * 
 	 * @param args The command line arguments.
 	 */
 	private void parseCommandLineOptions(String args[])
 	{	    	   
-	    CommandLineParser parser = new PosixParser();	    
+	    // Setup parser.
+	    CommandLineParser parser = new PosixParser();	
+	    
+	    // CommandLine to be loaded.
 	    CommandLine cl = null;
 
-	    // Parse arguments.
+	    // Parse CL arguments.
 	    try 
 	    {
 	        cl = parser.parse(options, args);
@@ -183,11 +232,19 @@
 	    {
 	        throw new RuntimeException(x);
 	    }
+	    	    	    
+	    // Turn on verbose mode.  This setting may be overridden by control section from XML.
+        if (cl.hasOption("v"))
+        {           
+            setVerbose(true);
+        }
+        // Turn on quiet mode.  If "-v" is also specified, this will have no effect. 
+        else if (cl.hasOption("q"))
+        {
+            setVerbose(false);
+        }
 	    
-	    // Init variable map.
-	    variableMap = new HashMap<String, String>();
-	    
-	    // Load properties file.
+	    // Load properties file containing variable definitions.
 	    if (cl.hasOption("p"))
 	    {
 	        String[] propValues = cl.getOptionValues("p");
@@ -220,7 +277,7 @@
 	        }
 	    }
 	    
-	    // Variable definitions.
+	    // Process user variable definitions.
 	    if (cl.hasOption("D"))
 	    {
 	        String[] defValues = cl.getOptionValues("D");
@@ -237,7 +294,37 @@
 	        }
 	    }
 	    
-	    // Extra argument is name of XML steering file.
+	    // Rewrite XML file with variables resolved.
+	    if (cl.hasOption("w"))
+	    {
+	        this.rewrite = true;
+	        String rewritePath = cl.getOptionValue("w");
+	        this.rewriteFile = new File(rewritePath);
+	        if (this.rewriteFile.exists())
+	        {
+	            throw new RuntimeException("Rewrite file already exists: " + rewritePath);
+	        }
+	    }
+	    
+	    // Set max number of events to run.
+	    if (cl.hasOption("n"))
+	    {
+	        this.maxEvents = Integer.valueOf(cl.getOptionValue("n"));
+	    }
+	    
+	    // Set number of events to skip. 
+	    if (cl.hasOption("s"))
+	    {
+	        this.skipEvents = Integer.valueOf(cl.getOptionValue("s"));
+	    }
+	    
+	    // Perform a dry run, not processing any events but doing job setup.
+	    if (cl.hasOption("x"))
+	    {
+	        this.performDryRun = true;
+	    }
+	    
+	    // The path to the XML steering file, which has no command switch.
 	    if (cl.getArgList().size() == 0)
 	    {
 	        throw new RuntimeException("Missing LCSim XML file argument.");
@@ -245,25 +332,30 @@
 	    else if (cl.getArgList().size() > 1)
 	    {
 	        throw new RuntimeException("Too many extra arguments.");
-	    }
+	    }	    
 	    
-	    // Setup steering file and check for existence.
+	    // Setup the XML steering file and check for its existence.
 	    File xmlRunControlFile = new File((String) cl.getArgList().get(0));
         if (!xmlRunControlFile.exists())
-          throw new RuntimeException("The steering file " + args[0] + " does not exist!");
+        {
+            throw new RuntimeException("The steering file " + args[0] + " does not exist!");
+        }
 	    
-	    // Setup from XML.
+	    // Setup job parameters from XML now that CL options are all processed.
 	    setup(xmlRunControlFile);
 	}
 	
 	/**
-	 * Add a variable definition to be substituted into the XML steering file.
+	 * Add a variable definition to be substituted into the job's XML file.
 	 * @param key The variable name.
 	 * @param value The variable's value.
 	 */
 	private void addVariableDefinition(String key, String value)
 	{
-	    //logStream.println(key + " = " + value);
+	    if (verbose)
+	    {
+	        logStream.println(key + " = " + value);
+	    }
 	    if (!this.variableMap.containsKey(key))
         {
             variableMap.put(key, value);
@@ -275,12 +367,23 @@
 	}
 
 	/**
-	 * Execute a job using the current steering state.
+	 * Execute a job using the current parameters.  This is publically accessible
+	 * in case the CL interface isn't being used and {@link #setup(File)} was called
+	 * manually.
 	 */
 	public void run()
 	{
+	    // Only one job can be run with the manager, after which a new one should be created.
+	    if (this.ranJob)
+	    {
+	        throw new RuntimeException("Cannot run multiple jobs!  Create another JobManager.");
+	    }
+	    
+	    // If setup was not called first, then abort the job.
 		if (!wasSetup)
-			throw new RuntimeException("Aborting job!  The setup() method was never called.");
+		{
+			throw new RuntimeException("Aborting job!  The method JobManager.setup(File) was never called.");
+		}
 		
 		if (printVersion)
 		{			
@@ -299,12 +402,15 @@
 			// Add the LCIO files to the loop.
 			loop.setLCIORecordSource(new LCIOEventSource("test", inputFiles));
 			
-			PrintStream statsStream = null;
-			
+			// Driver statistics printout.
+			PrintStream statsStream = null;			
 			if (printDriverStatistics)
+			{
 				statsStream = logStream;
+			}
 			
-			if (!dryRun)
+			// Execute job if not a dry run.
+			if (!performDryRun)
 			{
 				if (verbose)
 				{
@@ -315,7 +421,9 @@
 				if (skipEvents > 0)
 				{
 					if (verbose)
+					{
 						logStream.println("Skipping " + skipEvents + " events.");
+					}
 					loop.skip(skipEvents);
 				}
 
@@ -328,22 +436,30 @@
 					logStream.println("End time: " + (new Date()));
 				}
 			}
+			// A dry run does not process any events.  
 			else
 			{
+			    // Always print this info msg.
 				logStream.println("Executed dry run.  No events processed!");
 			}
 		} 
-		// Record source exhausted before number of events was hit.
+		// Record source was exhausted before max events was hit.  This is probably fine,
+		// but print message in case more were expected.
 		catch (LoopSourceExhaustedException x)
 		{
 		    logStream.println(x.getMessage());
 		}
-		// Other exceptions.
-		catch (Exception x)
+		// Catch other fatal exceptions thrown from the loop.
+		catch (Exception e)
 		{
-			throw new RuntimeException(x);
+			throw new RuntimeException(e);
 		}
+		
+		// Cleanup.
 		loop.dispose();
+		
+		// Set flag so that manager is NOT reused for another job. 
+		this.ranJob = true;
 	}
 	
 	/**
@@ -356,27 +472,18 @@
 	}
 	
 	/**
-	 * Return a list of Drivers to be executed.  This could be used to call them
-	 * from an external framework like JAS3.  The list will be empty unless the
-	 * <code>setup()</code> method has been called.
+	 * Return a list of Drivers to be executed.  This can be used from an external 
+	 * framework like JAS3.  The list will be empty unless the <code>setup()</code> 
+	 * method has been called.
 	 * @return A <code>List</code> of <code>Drivers</code>.
 	 */
 	public List<Driver> getDriverExecList()
 	{
 		return this.driverExec;
 	}
-
-	/**
-	 * Setup method which uses an embedded resource as the steering file.
-	 * @param resource
-	 */
-	public void setup(String resource)
-	{
-		setup(JobControlManager.class.getResourceAsStream(resource));
-	}
-
+	
 	/**
-	 * Setup method which uses a File.
+	 * Setup method from a <code>File</code>.
 	 * @param file
 	 */
 	public void setup(File file)
@@ -392,7 +499,8 @@
 	}
 
 	/**
-	 * Setup method which uses a <code>InputStream</code> for steering.  
+	 * Setup job parameters from an <code>InputStream</code> with XML text.	 
+	 * This could easily be used to load from a resource stream in a jar file.
 	 * @param in The XML input stream.
 	 */
 	public void setup(InputStream in)
@@ -405,10 +513,7 @@
         builder.setValidation(true);
         builder.setFeature("http://apache.org/xml/features/validation/schema", true);
 		
-        // Setup expression resolution.
-		factory = new JDOMExpressionFactory();		
-		if (paramConverter == null)
-			paramConverter = new ParameterConverters(factory);		
+        // Setup expression resolution. 		
 		builder.setFactory(factory);
 		
 		// Build the document.
@@ -425,26 +530,74 @@
 		// Setup the JobControlManager from the XML file.
 		setup(doc);
 	}
-		
+	
+	/**
+     * The primary setup method.  The other setup methods,
+     * {@link #setup(InputStream)} and {@link #setup(File)} both call
+     * this.  
+     * @param doc The lcsim recon XML document describing the job.
+     */
+    private void setup(Document doc)
+    {               
+        // Set the root element.
+        root = doc.getRootElement();
+        
+        // Do variable substitutions.
+        substituteVariables(doc);
+        
+        // Rewrite XML after variable substitution.  Correctness of values,
+        // such as existence of input file paths, is NOT checked.
+        if (this.rewrite)
+        {
+            rewriteXMLSteering(doc);
+        }
+                
+        // Setup the job control parameters.
+        setupJobControlParameters();
+
+        // Print system properties.
+        if (printSystemProperties)
+        {
+            printSystemProperties(logStream);
+        }
+        
+        // Setup the class loader.
+        setupClassLoader();
+    
+        // Setup units.
+        setupUnits();
+        
+        // Process the variable definitions.
+        processConstants();     
+                
+        // Setup drivers with parameters and execution order.
+        setupDrivers();
+
+        // Setup the file cache.
+        setupFileCache();
+
+        // Setup the input files.
+        setupInputFiles();
+
+        // Flag JobManager as setup.
+        wasSetup = true;
+    }
+          
 	/**
-	 * Perform variable substitution for an entire document.
+	 * Perform variable substitution within all text data in a entire document.
 	 * @param doc The XML document.
 	 */
 	private void substituteVariables(Document doc)
 	{
 	    substituteVariables(doc.getRootElement());
 	}
-	
-	// Pattern to extract variables from text of the form "${varName}".  Underscores
-	// and dashes are accepted in variable names, as well as the usual letters and numbers.
-	static private final Pattern varPattern = Pattern.compile("[$][{][a-zA-Z_-]*[}]");
-	
+		
 	/**
-	 * Substitute values from the previously filled internal <code>variableMap</code>
-	 * into an XML element and all its children, recursively.
+	 * Substitute values from the <code>variableMap</code> into an XML element 
+	 * and all its children, recursively.
 	 * 
 	 * @param element The XML element.
-	 * @throw RuntimeException If variable does not exist in the <code>variableMap</code>.
+	 * @throw RuntimeException If variable does not exist in the <code>variableMap</code>.
 	 */
 	private void substituteVariables(Element element)
 	{
@@ -454,11 +607,11 @@
 	        // Create a new matcher.
 	        Matcher match = varPattern.matcher(text);
 	        	        
-	        // No variables were used so return.
+	        // No variables were used.
 	        if (!match.find())
 	            return;
 	        
-	        // New text from current to contain resolved variable values.
+	        // Text data on which to perform substitutions.
             String newText = new String(text);
 	        
             // Reset the matcher.
@@ -473,10 +626,10 @@
                 // The name of the variable for lookup.
                 String varName = var.substring(2, var.length() - 1);
                 
-                // Get the value of the variable.
+                // The value of the variable.
                 String varValue = variableMap.get(varName);
                                      
-                // If a variable is not defined, then the application will immediately exit here.
+                // If a variable was not defined, then the application will immediately exit here.
                 if (varValue == null)
                 {
                     throw new RuntimeException("Variable not defined: " + varName);
@@ -486,95 +639,238 @@
                 newText = newText.replace(var, varValue);
             }
             
-            // Set the element's new text with the substituted values.
+            // Set this element's new text value.
             element.setText(newText);
 	    }
 	    
-	    // Recursively process all child elements.
+	    // Recursively process all child elements of this one.
 	    for (Iterator it = element.getChildren().iterator(); it.hasNext(); )
 	    {
 	        substituteVariables((Element)it.next());
 	    }
 	}
-
+	
 	/**
-	 * The primary setup method.  Public setup methods all end up here.
-	 * @param doc The lcsim recon XML document describing the job.
+	 * Rewrite the XML steering file after resolving variables (done externally).  
+	 * The output path is set by command line argument to "-w". 
+	 * @param doc The XML steering doc with variables substituted.
 	 */
-	private void setup(Document doc)
-	{		
-		// Clear and/or setup data structures for the next run.
-		clear();
-		
-		// Set the root element.
-		root = doc.getRootElement();
-		
-		// Do variable substitutions.
-		substituteVariables(doc);
-				
-		// Setup the job control parameters.
-		setupJobControlParameters();
-
-		// Print system properties.
-	    if (printSystemProperties)
-	            printSystemProperties(logStream);
-		
-		// Setup the class loader.
-		setupClassLoader();
+	private void rewriteXMLSteering(Document doc)
+	{
+	    if (verbose)
+	    {
+	        logStream.println("Rewriting XML to " + this.rewriteFile + " ...");
+	    }
+	    XMLOutputter outputter = new XMLOutputter();
+	    outputter.setFormat(Format.getPrettyFormat());	    
+	    try 
+	    {
+	        FileOutputStream out = new FileOutputStream(this.rewriteFile);
+	        outputter.output(doc, out);
+	        out.close();
+	    }
+	    catch (Exception e)
+	    {
+	        throw new RuntimeException(e);
+	    }
+	}
 	
-		// Setup units.
-        setupUnits();
-		
-        // Process the variable definitions.
-        processConstants();     
-				
-		// Setup drivers with parameters and execution order.
-		setupDrivers();
+	/**
+     * Setup the drivers from the XML job file, 
+     * adding them to the JobManager's <code>driverMap</code> .
+     */
+    private void setupDrivers()
+    {
+        if (printDriversDetailed)
+        {
+            logStream.println("--- Drivers ---");
+        }
+        
+        // Loop over the list of driver elements.
+        List<Element> drivers = root.getChild("drivers").getChildren("driver");
+        for (Element driver : drivers)
+        {
+            // Get the name of the Driver.
+            String name = driver.getAttributeValue("name");
 
-		// Setup the file cache.
-		setupFileCache();
+            // Get the fully qualified type of the Driver. ([packageName].[className])
+            String type = driver.getAttributeValue("type");
 
-		// Setup the input files.
-		setupInputFiles();
+            // Translate from a short name (optional).
+            if (availableDrivers.get(type) != null)
+            {
+                type = availableDrivers.get(type);
+            }
 
-		// Flag JobManager as setup.
-		wasSetup = true;
-	}
-	
-	/**
-	 * Get the map of numerical constants to values.
-	 * @return The map of numerical constants.
-	 */
-	public Map<String,Double> getConstants()
-	{
-		return this.constants;
-	}
+            //
+            // Get Class, BeanInfo, and Constructor, and setup the new Driver instance.
+            //
+            
+            // Class of the Driver.
+            Class driverClass;
+            try 
+            {
+                driverClass = loader.loadClass(type);
+            }
+            catch (ClassNotFoundException x)
+            {
+                throw new RuntimeException("The Driver class " + type + " was not found.", x);
+            }
+            
+            if (printDriversDetailed)
+                logStream.println(driverClass.getCanonicalName());
+                                    
+            // The Driver instance.
+            Driver newDriver;
+            try 
+            {
+                newDriver = (Driver) driverClass.newInstance();
+            }
+            catch (InstantiationException x)
+            {
+                throw new RuntimeException("Failed to create a Driver of class " + type + ".  " +
+                        "This class might be missing a public constructor with no arguments.",x);
+            }
+            catch (IllegalAccessException x)
+            {
+                throw new RuntimeException("Cannot access Driver type " + type + ". " +
+                        "You may need to make this class or its constructor public.",x);
+            }
 
-	/**
-	 * Reset private variables before next execution.
-	 */
-	private void clear()
-	{
-		inputFiles = new ArrayList<File>();
-		driverExec = new ArrayList<Driver>();
-		driverMap = new LinkedHashMap<String, Driver>();
-		maxEvents = -1;
-		cacheDirectory = null;
-		fileCache = null;
-		loader = null;
-		printDriverStatistics = false;
-		printSystemProperties = false;
-		printUserClassPath = false;
-		printDriversDetailed = false;
-		printInputFiles = true; // Defaults to true.
-		printVersion = false;
-		verbose = false;
-		wasSetup = false;
-		dryRun = false;
-		logStream = System.out;
-		loop = new LCSimLoop();
-		root = null;
-	}
+            // Get a list of Driver parameters.
+            List<Element> parameters = driver.getChildren();
+
+            // Process the parameters.
+            for (Element parameterElement : parameters)
+            {
+                // The parameter's setter method that we will find.
+                Method setter = null;
+                
+                // The parameter's type that will be inferred from the method or is given by a type string in
+                // the case of an overloaded method.
+                Class propertyType = null;
+                
+                // Get the parameter name.
+                String pname = parameterElement.getName();
+            
+                // Find setter methods that look like good matches for this parameter.
+                List<Method> methods = getSetterMethods(driverClass);
+                List<Method> methodCandidates = new ArrayList<Method>();
+                for (Method method : methods)
+                {                                  
+                    String propHack = method.getName().replace("set", "");
+                    propHack = propHack.substring(0, 1).toLowerCase() + propHack.substring(1);
+                    if (propHack.equals(pname))
+                    {
+                        methodCandidates.add(method);
+                    }
+                }
+                
+                // Found the single setter method.
+                if (methodCandidates.size() == 1)
+                {
+                    setter = methodCandidates.get(0);
+                    if (setter.getParameterTypes().length > 1)
+                    {
+                        throw new RuntimeException("The set method has too many arguments for parameter: " + pname);
+                    }
+                    propertyType = setter.getParameterTypes()[0];
+                }
+                // Found several, overloaded methods.  Try to disambiguate them.
+                else if (methodCandidates.size() > 1)
+                {
+                    if (parameterElement.getAttribute("type") == null)
+                        throw new RuntimeException("Parameter " + pname + " in Driver " + driverClass.getCanonicalName()
+                                + " is overloaded, but a type field is missing from the parameter's XML element.");
+                    try
+                    {
+                        // Try a primitive type first.
+                        propertyType = getPrimitiveType(parameterElement.getAttribute("type").getValue());
+
+                        // If type is null, then parameter is an Object and not a primitive, or it is not a valid type.
+                        if (propertyType == null)
+                            propertyType = Class.forName(parameterElement.getAttribute("type").getValue());
+                    }
+                    catch (ClassNotFoundException x)
+                    {
+                        throw new RuntimeException("Bad user type: " + parameterElement.getAttribute("type").getValue() + " for parameter " + pname + ".");
+                    }
+                    // Find a method that matches the user type.
+                    for (Method candidateMethod : methodCandidates)
+                    {
+                        if (candidateMethod.getParameterTypes().length == 1 && 
+                                candidateMethod.getParameterTypes()[0].equals(propertyType))
+                        {
+                            setter = candidateMethod;
+                            break;
+                        }
+                    }                                     
+                }
+                // No method found.  The parameter name is probably invalid.  
+                else if (methodCandidates.size() == 0)
+                {
+                    throw new RuntimeException("No set method found for parameter: " + pname);
+                }
+                
+                // No setter method found.
+                if (setter == null)
+                {
+                    throw new RuntimeException("Unable to find set method for parameter: " + pname);
+                }
+                
+                // Convert the parameter to the appropriate type.
+                IParameterConverter converter = paramConverter.getConverterForType(propertyType);
+                if (converter == null)
+                {
+                    throw new RuntimeException(
+                            "No converter found for parameter " + parameterElement.getName() + 
+                            " with type " + propertyType.getName() + ".");
+                }
+                Object nextParameter = converter.convert(factory, parameterElement);
+                                
+                // Call the setter with the parameter as argument.
+                Object pargs[] = new Object[1];
+                pargs[0] = nextParameter;
+                try
+                {  
+                    // This invokes the setter method of the driver.
+                    setter.invoke(newDriver, pargs);
+                    
+                    // Print parameters and values as they are set.
+                    if (printDriversDetailed)
+                        logStream.println("    " + pname + " = " + parameterElement.getText().trim());
+                } 
+                catch (Exception x)
+                {
+                    throw new RuntimeException("Problem processing parameter " + parameterElement.getName() + ".", x);
+                }                                   
+            } // parameter loop
+            
+            // Add a driver to the manager.
+            addDriver(name, newDriver);
+        } // driver loop
+        
+        if (printDriversDetailed)
+        {
+            logStream.println("--- End Drivers ---");
+        }
+
+        // Make a list of Drivers to be executed.
+        List<Element> exec = root.getChild("execute").getChildren("driver");
+        for (Element execDriver : exec)
+        {
+            String driverName = execDriver.getAttributeValue("name");
+            Driver driverFind = driverMap.get(driverName);
+            if (driverFind != null)
+            {
+                driverExec.add(driverFind);
+            }
+            else
+            {
+                throw new RuntimeException("A Driver called " + driverName + " was not found.");
+            }
+        }
+    }
 	
 	/**
 	 * Add a Driver to the Driver map.
@@ -585,7 +881,7 @@
 	{
 		if (driverMap.containsKey(name))
 		{
-			throw new RuntimeException("ERROR: duplicate driver name: " + name);
+			throw new RuntimeException("Duplicate driver name: " + name);
 		}
 		driverMap.put(name, driver);
 	}
@@ -601,6 +897,12 @@
 		logStream.println();
 	}
 	
+	/**
+	 * Cleaup file text and add to file list.
+	 * @param fileText The file's raw text path from XML file.
+	 * @param fileList The list files to which new file will be appended.
+	 * @return The file that was created from the text.
+	 */
 	private File processFileText(String fileText, List<File> fileList)
 	{
 	    Element fileElement = new Element("file");
@@ -658,7 +960,7 @@
 	}
 		
 	/**
-	 * Setup the input files to be processed using the XML steering document.
+	 * Setup the list of input files to be processed from the XML job file.
 	 */
 	@SuppressWarnings("unchecked")
     private void setupInputFiles()
@@ -699,7 +1001,7 @@
 			}			
 		}
 		
-		// Read fileSets.
+		// Process <fileSet> elements.
 		List<Element> fileSets = (List<Element>)root.getChild("inputFiles").getChildren("fileSet");
 		for (Element fileSet : fileSets)
 		{
@@ -717,7 +1019,7 @@
 		    }
 		}
 		
-		// Read fileRegExps, which may only reference local files.
+		// Read <fileRegExp> elements, which may only reference local files, not URLs.
 		List<Element> fileRegExps = (List<Element>)root.getChild("inputFiles").getChildren("fileRegExp");
 		for (Element fileRegExp : fileRegExps)
 		{
@@ -728,7 +1030,9 @@
 		        throw new RuntimeException(basedir + " is not a valid directory!");
 		    String dirlist[] = dir.list();
 		    if (verbose)
+		    {
 		        logStream.println();
+		    }
 		    for (String file : dirlist)
 		    {
 		        if (file.endsWith(".slcio"))
@@ -738,28 +1042,32 @@
 		            {
 		                processFileText(basedir + File.separator + file, inputFiles);
 		                if (verbose)
-		                    logStream.println("Matched file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");   
+		                {
+		                    logStream.println("Matched file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");
+		                }
 		            }
 		            else
 		            {
 		                if (verbose)
+		                {
 		                    logStream.println("Did NOT match file <" + file.toString() + "> to pattern <" + pattern.toString() + ">");
+		                }
 		            }
 		        }
 		    }
 		}
 		
-		// Check that all files exist.
+		// Check that all the files exist.
 		for (File file : inputFiles)
         {
             if (!file.exists())
             {
-                logStream.println("The file " + file.getAbsolutePath() + " does not exist!");
-                throw new RuntimeException("The file " + file.getAbsolutePath() + " does not exist!");
+                logStream.println("The input file " + file.getAbsolutePath() + " does not exist!");
[truncated at 1000 lines; 313 more skipped]
CVSspam 0.2.8