Print

Print


Commit in lcsim/src/org/lcsim/job on MAIN
JobControlManager.java+329-421.48 -> 1.49
add ability to make variable substitutions into xml steering files (see class description for details); add additional comments

lcsim/src/org/lcsim/job
JobControlManager.java 1.48 -> 1.49
diff -u -r1.48 -r1.49
--- JobControlManager.java	9 Nov 2010 22:26:00 -0000	1.48
+++ JobControlManager.java	31 Mar 2011 01:36:38 -0000	1.49
@@ -16,13 +16,21 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.PosixParser;
 import org.freehep.application.studio.Studio;
 import org.freehep.jas.services.DynamicClassLoader;
 import org.freehep.record.loop.LoopSourceExhaustedException;
@@ -42,41 +50,68 @@
 import org.lcsim.util.xml.JDOMExpressionFactory;
 
 /**
- * The <code>JobControlManager</code> provides an XML frontend for running LCSim jobs.
- * The command line syntax is <code>java -jar ./lib/lcsim.jar steeringFile.xml</code>.
+ * Provides a frontend for running LCSim jobs using an XML steering file.
  * 
- * @author jeremym
- * @version $Id: JobControlManager.java,v 1.48 2010/11/09 22:26:00 jeremy Exp $
+ * The command line syntax is 
+ * <code>java -jar ./lib/lcsim-bin.jar -pPropFile.prop -Dvar=value steeringFile.xml</code>.
+ * 
+ * The <code>p</code> option is for loading external variable definitions from a 
+ * properties file.  An unlimited number of properties files can be specified.
+ * 
+ * The <code>D</code> option can be used to set variable definitions that will be
+ * 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).  
+ *   
+ * @version $Id: JobControlManager.java,v 1.49 2011/03/31 01:36:38 jeremy Exp $
+ * @author Jeremy McCormick
  */
 public class JobControlManager
 {
-	Map<String, Driver> driverMap;
-	List<Driver> driverExec;
-	List<File> inputFiles;
-	Map<String, String> availableDrivers = new HashMap<String, String>();
-	int maxEvents = -1;
-	int skipEvents = -1;
-	File cacheDirectory;
-	FileCache fileCache; // Start with default dir.
-	ClassLoader loader;
-	boolean printInputFiles;
-	boolean printDriverStatistics;
-	boolean printSystemProperties;
-	boolean printUserClassPath;
-	boolean printDriversDetailed;
-	boolean printVersion;
-	boolean verbose;
-	boolean wasSetup;
-	boolean dryRun;
-	PrintStream logStream = System.out;
-	LCSimLoop loop;
-	Element root;
-	Map<String,Double> constants = new HashMap<String,Double>();
-	JDOMExpressionFactory factory;
+    private LCSimLoop loop;
+    
+    private Map<String, Driver> driverMap;
+	private List<Driver> driverExec;	
+	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>();
+	
+	private int maxEvents = -1;
+	private int skipEvents = -1;
+	
+	private File cacheDirectory;
+	private FileCache fileCache; // Start with default dir.
+	
+	private ClassLoader loader;
+	
+	// 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;
+	
+	// The log stream.
+	private PrintStream logStream = System.out;
+	
+	private Element root;	
+	
+	private JDOMExpressionFactory factory;
+	
 	private static ParameterConverters paramConverter;
 	
+	private Options options;
+	
 	/**
-	 * Default no-argument constructor.
+	 * This is the default no-argument constructor.
 	 */
 	public JobControlManager()
 	{
@@ -88,33 +123,164 @@
 	    {
 	        throw new RuntimeException(x);
 	    }
+	    
+	    // Create Options object to represent command-line args.
+	    this.options = createCommandLineOptions();
 	}
 	
 	/**
-	 * Simple frontend method taking the name of the XML steering file.
+	 * Run the manager using a main.  Takes command-line options (see class description).
 	 * @param args The command line arguments.
 	 */
 	public static void main(String args[])
-	{
-		if (args.length == 0)
-			throw new RuntimeException("Missing XML input file argument!");
-		File xmlRunControlFile = new File(args[0]);
-		if (!xmlRunControlFile.exists())
-			throw new RuntimeException("The file " + args[0] + " does not exist!");
+	{		
+	    // 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();
-		mgr.setup(xmlRunControlFile);
+		mgr.parseCommandLineOptions(args);
 		mgr.run();
 	}
+	
+	/**
+	 * Create the command line options.
+	 * @return The command line options.
+	 */
+	private 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.");
+	    
+	    options.addOption(propOpt);
+	    options.addOption(defOpt);
+
+	    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.
+	 * 
+	 * @param args The command line arguments.
+	 */
+	private void parseCommandLineOptions(String args[])
+	{	    	   
+	    CommandLineParser parser = new PosixParser();	    
+	    CommandLine cl = null;
+
+	    // Parse arguments.
+	    try 
+	    {
+	        cl = parser.parse(options, args);
+	    }
+	    catch (ParseException x)
+	    {
+	        throw new RuntimeException(x);
+	    }
+	    
+	    // Init variable map.
+	    variableMap = new HashMap<String, String>();
+	    
+	    // Load properties file.
+	    if (cl.hasOption("p"))
+	    {
+	        String[] propValues = cl.getOptionValues("p");
+	        for (String propFileName : propValues)
+	        {
+	            InputStream in = null;
+	            try 
+	            {
+	                 in = new FileInputStream(propFileName);
+	            }
+	            catch (FileNotFoundException e)
+	            {
+	                throw new RuntimeException(e);
+	            }
+	            Properties props = new Properties();
+	            try
+                {
+                    props.load(in);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+                for (Entry<Object,Object> entry : props.entrySet())
+                {
+                    String key = (String) entry.getKey();
+                    String value = (String) entry.getValue();
+                    addVariableDefinition(key, value);
+                }
+	        }
+	    }
+	    
+	    // Variable definitions.
+	    if (cl.hasOption("D"))
+	    {
+	        String[] defValues = cl.getOptionValues("D");
+	        for (String def : defValues)
+	        {
+	            String[] s = def.split("=");
+	            if (s.length != 2)
+	            {
+	                throw new RuntimeException("Bad variable format: " + def);
+	            }
+	            String key = s[0];
+	            String value = s[1];
+	            addVariableDefinition(key, value);
+	        }
+	    }
+	    
+	    // Extra argument is name of XML steering file.
+	    if (cl.getArgList().size() == 0)
+	    {
+	        throw new RuntimeException("Missing LCSim XML file argument.");
+	    }
+	    else if (cl.getArgList().size() > 1)
+	    {
+	        throw new RuntimeException("Too many extra arguments.");
+	    }
+	    
+	    // Setup steering file and check for existence.
+	    File xmlRunControlFile = new File((String) cl.getArgList().get(0));
+        if (!xmlRunControlFile.exists())
+          throw new RuntimeException("The steering file " + args[0] + " does not exist!");
+	    
+	    // Setup from XML.
+	    setup(xmlRunControlFile);
+	}
+	
+	/**
+	 * Add a variable definition to be substituted into the XML steering file.
+	 * @param key The variable name.
+	 * @param value The variable's value.
+	 */
+	private void addVariableDefinition(String key, String value)
+	{
+	    //logStream.println(key + " = " + value);
+	    if (!this.variableMap.containsKey(key))
+        {
+            variableMap.put(key, value);
+        }
+        else
+        {
+            throw new RuntimeException("Duplicate variable definition: " + key);
+        }
+	}
 
 	/**
-	 * User method for executing the job if JobControlManager is being 
-	 * explicitly instantiated rather than created by calling the {#{@link JobControlManager#main(String[])}.
-	 * method.
+	 * Execute a job using the current steering state.
 	 */
 	public void run()
 	{
 		if (!wasSetup)
-			throw new RuntimeException("Aborting job!  The setup method was never called.");
+			throw new RuntimeException("Aborting job!  The setup() method was never called.");
 		
 		if (printVersion)
 		{			
@@ -255,10 +421,81 @@
 		{
 			throw new RuntimeException(x);
 		}
-		
+				        			
 		// Setup the JobControlManager from the XML file.
 		setup(doc);
 	}
+		
+	/**
+	 * Perform variable substitution for an 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.
+	 * 
+	 * @param element The XML element.
+	 * @throw RuntimeException If variable does not exist in the <code>variableMap</code>.
+	 */
+	private void substituteVariables(Element element)
+	{
+	    String text = element.getTextNormalize();	    
+	    if (text.length() != 0)
+	    {
+	        // Create a new matcher.
+	        Matcher match = varPattern.matcher(text);
+	        	        
+	        // No variables were used so return.
+	        if (!match.find())
+	            return;
+	        
+	        // New text from current to contain resolved variable values.
+            String newText = new String(text);
+	        
+            // Reset the matcher.
+	        match.reset();
+	        
+	        // Loop over the matches.
+            while (match.find())
+            {
+                // Variable string with ${...} enclosure included.
+                String var = match.group();
+                
+                // The name of the variable for lookup.
+                String varName = var.substring(2, var.length() - 1);
+                
+                // Get the value of the variable.
+                String varValue = variableMap.get(varName);
+                                     
+                // If a variable is not defined, then the application will immediately exit here.
+                if (varValue == null)
+                {
+                    throw new RuntimeException("Variable not defined: " + varName);
+                }
+                
+                // Substitute this variable's value into the text.
+                newText = newText.replace(var, varValue);
+            }
+            
+            // Set the element's new text with the substituted values.
+            element.setText(newText);
+	    }
+	    
+	    // Recursively process all child elements.
+	    for (Iterator it = element.getChildren().iterator(); it.hasNext(); )
+	    {
+	        substituteVariables((Element)it.next());
+	    }
+	}
 
 	/**
 	 * The primary setup method.  Public setup methods all end up here.
@@ -266,11 +503,14 @@
 	 */
 	private void setup(Document doc)
 	{		
-		// Clear data structures in case a previous job was run.
+		// 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();
@@ -301,6 +541,10 @@
 		wasSetup = true;
 	}
 	
+	/**
+	 * Get the map of numerical constants to values.
+	 * @return The map of numerical constants.
+	 */
 	public Map<String,Double> getConstants()
 	{
 		return this.constants;
@@ -364,6 +608,12 @@
 	    return processFileElement(fileElement, fileList);
 	}
 	
+	/**
+	 * Create a <code>File</code> object from the text in an XML element.
+	 * @param fileElement The element containing a file path or URL.
+	 * @param fileList List to append new <code>File</code>.
+	 * @return The <code>File</code> object.
+	 */
 	private File processFileElement(Element fileElement, List<File> fileList)
 	{
 	    String fileLoc = processPath(fileElement.getText().trim());
@@ -407,6 +657,9 @@
         return file;
 	}
 		
+	/**
+	 * Setup the input files to be processed using the XML steering document.
+	 */
 	@SuppressWarnings("unchecked")
     private void setupInputFiles()
 	{
@@ -524,6 +777,9 @@
         }		
 	}
 	
+	/**
+	 * Setup the job control parameters using the XML steering document.
+	 */
 	private void setupJobControlParameters()
 	{
 	    Element control = root.getChild("control");
@@ -679,6 +935,9 @@
 	    }								
 	}	
 	
+	/**
+	 * Setup the manager's class loader.
+	 */
 	private void setupClassLoader()
 	{
 		Element classpath = root.getChild("classpath");
@@ -752,6 +1011,9 @@
 		}				
 	}
 	
+	/**
+	 * Setup the file cache.
+	 */
 	private void setupFileCache()
 	{
 	    if (cacheDirectory == null)
@@ -768,6 +1030,9 @@
 		}
 	}
 	
+	/**
+	 * Setup the drivers required by this job.
+	 */
 	private void setupDrivers()
 	{
 		if (printDriversDetailed)
@@ -953,6 +1218,9 @@
 		}
 	}
 		
+	/**
+	 * Create the constants from the XML file.
+	 */
 	private void processConstants()
 	{
 		Element define = root.getChild("define");		
@@ -969,6 +1237,9 @@
 		}
 	}
 	
+	/**
+	 * Setup the system of units.
+	 */
 	private void setupUnits()
 	{
 		Constants constants = Constants.getInstance();
@@ -978,6 +1249,11 @@
 	    }
 	}
 	
+	/**
+	 * Get the Java primitive type class from a type name.
+	 * @param name The name of the type.
+	 * @return The primitive type class.
+	 */
 	private static Class getPrimitiveType(String name)
 	{
 		if (name.equals("byte")) return byte.class;
@@ -992,6 +1268,11 @@
 		return null;
 	}		
 	
+	/**
+	 * Get the setter methods of a class.
+	 * @param klass The class.
+	 * @return A list of setter methods.
+	 */
 	public List<Method> getSetterMethods(Class klass)
 	{
 	    List<Method> methods = new ArrayList<Method>();
@@ -1010,6 +1291,12 @@
 	    return methods;
 	}
 
+	/**
+	 * Process the path string to substitute in the user's home directory for
+	 * the "~" character.
+	 * @param path The original path.
+	 * @return The path with home dir substitution.
+	 */
     public String processPath(String path)
     {
         if (path.startsWith("~"))
CVSspam 0.2.8