Commits

Anonymous committed ca34f6d

Added ECAT edit language; for now, it's a stub language that only allows variable initialization, which is useful in the upload applet

  • Participants
  • Parent commits 1e18f1d

Comments (0)

Files changed (25)

File ecat-edit/.classpath

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry kind="src" path="target/generated-sources/antlr3"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

File ecat-edit/.project

+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>ecat-edit</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>

File ecat-edit/.settings/org.eclipse.jdt.core.prefs

+#Thu Feb 11 10:13:20 CST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.4
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.source=1.4

File ecat-edit/.settings/org.maven.ide.eclipse.prefs

+#Thu Feb 11 10:13:20 CST 2010
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1

File ecat-edit/pom.xml

+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.nrg</groupId>
+  <artifactId>ecat-edit</artifactId>
+  <packaging>jar</packaging>
+  <version>0.1-SNAPSHOT</version>
+  <name>ecat-edit</name>
+  <parent>
+    <groupId>org.nrg</groupId>
+    <artifactId>ecat</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+	<artifactId>maven-compiler-plugin</artifactId>
+	<configuration>
+	  <source>1.4</source>
+	  <target>1.4</target>
+	</configuration>
+      </plugin>
+      <plugin>
+	<groupId>org.antlr</groupId>
+	<artifactId>antlr3-maven-plugin</artifactId>
+	<version>3.1.3-1</version>
+	<executions>
+          <execution>
+            <goals>
+              <goal>antlr</goal>
+            </goals>
+          </execution>
+	</executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.nrg</groupId>
+      <artifactId>ecat-io</artifactId>
+      <version>0.1-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.15</version>
+    </dependency>
+    <dependency>
+      <groupId>org.antlr</groupId>
+      <artifactId>antlr-runtime</artifactId>
+      <version>3.2</version>
+    </dependency>
+  </dependencies>
+</project>

File ecat-edit/src/main/antlr3/org/nrg/ecat/edit/EditECATASTParser.g

+tree grammar EditECATASTParser;
+
+options {
+	tokenVocab = EditECATParser;
+	ASTLabelType = CommonTree;
+}
+
+@header {
+	package org.nrg.ecat.edit;
+	
+	import java.io.IOException;
+	import java.util.Collections;
+	import java.util.Map;
+	import java.util.HashMap;
+	import java.util.TreeMap;
+}
+
+@members {
+	private final Map variables = new TreeMap();
+	private final Map functions = new HashMap();
+	
+  /**
+   * Adapted from JSON interpreter string extractor by Richard Clark
+   **/
+  private static String extractString(final CommonTree token) {
+    final StringBuffer sb = new StringBuffer(token.getText());
+    
+    sb.deleteCharAt(0); // remove leading and trailing quotes
+    sb.deleteCharAt(sb.length() - 1);
+    
+    int i = 0;
+    while (i < sb.length() - 1) {
+      final int iesc = sb.indexOf("\\", i);  // look for a backslash
+      if (-1 == iesc) {
+        break;  // no backlashes found; move on
+      }
+      
+      final int cesc = sb.charAt(iesc + 1);
+      switch (cesc) {
+        case 'b': // backspace
+          sb.replace(iesc, iesc + 2, "\b");
+          break;
+          
+        case 't': // tab
+          sb.replace(iesc, iesc + 2, "\t");
+          break;
+          
+        case 'n': // newline
+          sb.replace(iesc, iesc + 2, "\n");
+          break;
+          
+        case 'f': // form feed
+          sb.replace(iesc, iesc + 2, "\f");
+          break;
+          
+        case 'r': // return
+          sb.replace(iesc, iesc + 2, "\r");
+          break;
+          
+        case '"': // double quote
+          sb.replace(iesc, iesc + 2, "\"");
+          break;
+          
+        case '\'':  // single quote
+          sb.replace(iesc, iesc + 2, "'");
+          break;
+          
+        case '\\':  // backslash
+          sb.replace(iesc, iesc + 2, "\\");
+          break;
+          
+        case 'u': // unicode escape
+          throw new UnsupportedOperationException();
+          
+        case '0': // octal escape
+          throw new UnsupportedOperationException();
+        
+        default:
+          throw new UnsupportedOperationException("no support for escape code \\" + cesc);
+       }
+       i = iesc + 1;
+    }
+    
+    return sb.toString();
+  }
+	
+	private Variable getVariable(final CommonTree token) {
+		return getVariable(token.getText());
+	}
+		
+	public Variable getVariable(final String label) {
+		final String canonical = label.toLowerCase();
+		if (variables.containsKey(canonical)) {
+			return (Variable)variables.get(canonical);
+		} else {
+			final Variable v = new ScriptReferencedVariable(label);
+			variables.put(canonical, v);
+			return v;
+		}
+	}
+	
+	public final Map getVariables() {
+		return Collections.unmodifiableMap(variables);
+	}
+	
+	public void setFunction(final String label, final ScriptFunction f) {
+		functions.put(label, f);
+	}
+		
+	private Value apply(final String name, final List termlist)
+	throws ScriptEvaluationException {
+		final ScriptFunction f = (ScriptFunction)functions.get(name);
+		if (null == f) {
+			throw new ScriptEvaluationException("undefined function " + name);
+		} else {
+			return f.apply(termlist);
+    		}
+	}
+}
+
+script
+	: statement*
+	;
+
+statement
+	:	action
+	|	description
+	|	initialization
+	;
+	
+action 
+	:	echo
+	;
+
+initialization
+	:	^(INITIALIZE ID value) {
+			try {
+				getVariable($ID).setInitialValue($value.v);
+			} catch (MultipleInitializationException e) {
+				e.printStackTrace();
+				throw new RecognitionException();	// TODO: better
+			}
+		}
+	;
+	
+description
+	:	^(DESCRIBE id=ID desc=STRING) {
+			getVariable(id).setDescription(extractString(desc));
+		}
+	|	^(HIDDEN id=ID) {
+			getVariable(id).setIsHidden(true);
+		}
+	;
+	
+echo
+	:	^(ECHO v=value) {
+			try {
+            			System.out.println(v.on(Collections.EMPTY_MAP));
+			} catch (ScriptEvaluationException e) {
+				e.printStackTrace();
+			}
+		}
+	|	ECHO {
+           		System.out.println();
+		};
+
+term returns [Value v]
+	:	STRING	{ $v = new ConstantValue(extractString($STRING)); }
+	|	NUMBER	{ $v = new ConstantValue(Integer.valueOf($NUMBER.getText())); }
+	|	ID		{ $v = new VariableValue(getVariable($ID)); }
+	|	^(FUNCTION ID termlist)	{
+	   		try {
+	   			$v = apply($ID.getText(), $termlist.tl);
+	   		} catch (ScriptEvaluationException e) {
+	   			e.printStackTrace();
+	   			throw new RecognitionException(input);	// TODO: better
+	   		}
+	   	}
+	;
+	
+termlist returns [List tl]
+	@init {
+		tl = new ArrayList();
+	}
+	:	(t=term { tl.add(t); })*
+	;
+	
+value returns [Value v]
+ 	:	t=term { $v = t; }
+	;

File ecat-edit/src/main/antlr3/org/nrg/ecat/edit/EditECATLexer.g

+lexer grammar EditECATLexer;
+
+@header {
+	package org.nrg.ecat.edit;
+}
+
+LEFT	:	'[';
+RIGHT	:	']';
+COMMA	:	',';
+
+CONSTRAINS :	':';
+DELETE	:	'-';
+EQUALS	:	'=';
+MATCHES	:	'~';
+ASSIGN	:	':=';
+
+ECHO	:	'echo';
+NEW	:	'new';
+DESCRIBE:	'describe';
+HIDDEN	:	'hidden';
+
+ID  :	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
+    ;
+
+COMMENT
+    :   '//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}
+    ;
+
+WS  :   ( ' '
+        | '\t'
+        | ('\\' '\r'? '\n')
+        ) {$channel=HIDDEN;}
+    ;
+    
+NEWLINE	:	'\r'? '\n';
+
+
+
+STRING
+    :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
+    ;
+
+NUMBER	:	'0' | ('-'? '1'..'9' DIGIT*);
+
+fragment LPAREN	:	'(';
+
+fragment RPAREN	:	')';
+
+fragment DIGIT	:	('0'..'9');
+
+fragment HEXDIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
+
+fragment
+ESC_SEQ
+    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
+    |   UNICODE_ESC
+    |   OCTAL_ESC
+    ;
+
+fragment
+OCTAL_ESC
+    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
+    |   '\\' ('0'..'7') ('0'..'7')
+    |   '\\' ('0'..'7')
+    ;
+
+fragment
+UNICODE_ESC
+    :   '\\' 'u' HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT
+    ;

File ecat-edit/src/main/antlr3/org/nrg/ecat/edit/EditECATParser.g

+parser grammar EditECATParser;
+
+options {
+	output = AST;
+	tokenVocab = EditECATLexer;
+}
+
+tokens {
+	FUNCTION;
+	INITIALIZE;
+}
+
+@header {
+	package org.nrg.ecat.edit;
+}
+	
+script	:	(NEWLINE!* statement)* NEWLINE!* EOF!;
+
+statement
+	:	action
+	|	initialization
+	|	description
+	;
+
+action	:	echo;
+
+echo	:	ECHO value -> ^(ECHO value)
+	|	ECHO
+	;
+
+initialization
+	:	ID ASSIGN value -> ^(INITIALIZE ID value)
+	;
+	
+description
+	:	DESCRIBE ID STRING -> ^(DESCRIBE ID STRING)
+	|	DESCRIBE ID HIDDEN -> ^(HIDDEN ID)
+	;
+	
+term	:	STRING
+	|	NUMBER
+	|	ID LEFT termlist RIGHT -> ^(FUNCTION ID termlist)
+	|	ID
+	;
+	
+termlist 
+	:	term (COMMA! term)*
+	;
+	
+value 	:	term
+	;

File ecat-edit/src/main/java/org/nrg/ecat/edit/AbstractIndexedLabelFunction.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public abstract class AbstractIndexedLabelFunction implements ScriptFunction {
+	protected abstract boolean isAvailable(String label) throws ScriptEvaluationException;
+	
+	private final Value getFormat(final List values) throws ScriptEvaluationException {
+		try {
+		return (Value)values.get(0);
+		} catch (IndexOutOfBoundsException e) {
+			try {
+				throw new ScriptEvaluationException(this.getClass().getField("name").get(null)
+						+ " requires format argument");
+			} catch (IllegalArgumentException e1) {
+				throw new RuntimeException(e1);
+			} catch (SecurityException e1) {
+				throw new RuntimeException(e1);
+			} catch (IllegalAccessException e1) {
+				throw new RuntimeException(e1);
+			} catch (NoSuchFieldException e1) {
+				throw new RuntimeException(e1);
+			}
+		}
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.ScriptFunction#apply(java.util.List)
+	 */
+	public Value apply(final List args) throws ScriptEvaluationException {
+		final Value format = getFormat(args);
+		return new Value() {
+			private NumberFormat buildFormatter(final int len) {
+				final StringBuffer sb = new StringBuffer();
+				for (int i = 0; i < len; i++) {
+					sb.append("0");
+				}
+				return new DecimalFormat(sb.toString());
+			}
+			
+			private String valueFor(final Object fo) throws ScriptEvaluationException {
+				final String format = fo.toString();
+				final int offset = format.indexOf('#');
+				int end = offset;
+				while ('#' == format.charAt(end)) {
+					end++;
+				}
+				final NumberFormat nf = buildFormatter(end - offset);
+				final StringBuffer sb = new StringBuffer(format);
+				for (int i = 0; true; i++) {
+					final String label = sb.replace(offset, end, nf.format(i)).toString();
+					if (isAvailable(label)) {
+						return label;
+					}
+				}
+			}
+			
+			public Object on(Map m) throws ScriptEvaluationException {
+				return valueFor(format.on(m));
+			}
+			
+			public Set getVariables() {
+				return format.getVariables();
+			}
+		};
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/ConstantValue.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class ConstantValue implements Value {
+	private final Object value;
+	
+	public ConstantValue(final Object value) {
+		this.value = value;
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#getVariables()
+	 */
+	public Set getVariables() { return Collections.EMPTY_SET; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#on(java.util.Map)
+	 */
+	public Object on(final Map m) { return value; }
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/IntegerValue.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class IntegerValue implements Value {
+	private final String sval;
+	private final int val;
+	
+	public IntegerValue(final String value) {
+		sval = value;
+		val = Integer.parseInt(value);
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#getVariables()
+	 */
+	public Set getVariables() { return Collections.EMPTY_SET; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#on(java.util.Map)
+	 */
+	public Object on(Map m) { return sval; }
+
+	public int getValue() { return val; }
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/MessageFormatValue.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class MessageFormatValue implements Value {
+	private final Value format;
+	private final List values;
+	private final Set variables;
+	
+	public MessageFormatValue(final Value format, final Collection values) {
+		this.format = format;
+		this.values = new ArrayList(values);
+		
+		// Sort out the dependencies
+		final Set tv = new LinkedHashSet();
+		for (final Iterator i = this.values.iterator(); i.hasNext(); ) {
+			final Value v = (Value)i.next();
+			tv.addAll(v.getVariables());
+		}
+		this.variables = Collections.unmodifiableSet(tv);
+	}
+	
+	public MessageFormatValue(final String format, final Collection values) {
+		this(new ConstantValue(format), values);
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#getVariables()
+	 */
+	public Set getVariables() { return variables; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#on(java.util.Map)
+	 */
+	public Object on(Map m) throws ScriptEvaluationException {
+		final Object[] vals = new Object[values.size()];
+		for (int i = 0; i < values.size(); i++) {
+			vals[i] = ((Value)values.get(i)).on(m);
+		}
+		final Object f = format.on(m);
+		return MessageFormat.format(null == f ? "" : f.toString(), vals);
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/MultipleInitializationException.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class MultipleInitializationException extends Exception {
+	private static final long serialVersionUID = 1L;
+	
+	public MultipleInitializationException(final Variable v, final Value value) {
+		super("Attempted to reinitialize variable " + v + " -> " + value);
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/ScriptApplicator.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.antlr.runtime.ANTLRInputStream;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.RecognitionException;
+import org.antlr.runtime.tree.CommonTree;
+import org.antlr.runtime.tree.CommonTreeNodeStream;
+import org.apache.log4j.Logger;
+import org.nrg.ecat.edit.fn.Format;
+import org.nrg.ecat.edit.fn.Lowercase;
+import org.nrg.ecat.edit.fn.Substring;
+import org.nrg.util.GraphUtils;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class ScriptApplicator {
+	private final Logger logger = Logger.getLogger(ScriptApplicator.class);
+	private final EditECATASTParser astParser;
+
+	public ScriptApplicator(final InputStream script, final Map functions)
+	throws IOException,ScriptEvaluationException {
+		if (null == script) {
+			astParser = null;
+		} else {
+			try {
+				logger.trace("setting up ECAT edit lexer");
+				final EditECATLexer lexer = new EditECATLexer(new ANTLRInputStream(script));
+				logger.trace("setting up ECAT edit parser");
+				final EditECATParser parser = new EditECATParser(new CommonTokenStream(lexer));
+				logger.trace("parsing ECAT edit to AST");
+				final EditECATParser.script_return sr = parser.script();
+
+				final CommonTree ast = (CommonTree)sr.getTree();
+				if (null == ast) {
+					logger.trace("unparseable");
+					astParser = null;
+				} else {
+					astParser = new EditECATASTParser(new CommonTreeNodeStream(ast));
+					logger.trace("adding functions: substring, format, lowercase");
+					astParser.setFunction(Substring.name, new Substring());
+					astParser.setFunction(Format.name, new Format());
+					astParser.setFunction(Lowercase.name, new Lowercase());
+					for (final Iterator mei = functions.entrySet().iterator(); mei.hasNext(); ) {
+						final Map.Entry me = (Map.Entry)mei.next();
+						logger.trace("adding function " + me);
+						astParser.setFunction((String)me.getKey(), (ScriptFunction)me.getValue());
+					}
+					logger.trace("parsing ECAT edit AST");
+					astParser.script();
+				}
+			} catch (RecognitionException e) {
+				throw new ScriptEvaluationException("error parsing script", e);
+			}
+		}
+	}
+	
+	public Variable getVariable(final String label) {
+		return astParser.getVariable(label);
+	}
+
+	public Map getVariables() {
+		return astParser.getVariables();
+	}
+
+	public List getSortedVariables(final Collection excluding) {
+		final Map graph = new LinkedHashMap();
+		for (final Iterator i = astParser.getVariables().values().iterator(); i.hasNext(); ) {
+			final Variable v = (Variable)i.next();
+			if (!excluding.contains(v)) {
+				final Value iv = v.getInitialValue();
+				final Collection dependencies = new ArrayList();
+				if (null != iv) {
+					for (final Iterator di = iv.getVariables().iterator(); di.hasNext(); ) {
+						final Variable dv = (Variable)di.next();
+						if (!excluding.contains(dv.getName())) {
+							dependencies.add(dv);
+						}
+					}
+				}
+				graph.put(v, dependencies);
+			}
+		}
+		return GraphUtils.topologicalSort(graph);
+	}
+
+
+	public List getSortedVariables(final String[] excluding) {
+		return getSortedVariables(Arrays.asList(excluding));
+	}
+
+	public List getSortedVariables() {
+		return this.getSortedVariables(Collections.EMPTY_LIST);
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/ScriptEvaluationException.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class ScriptEvaluationException extends Exception {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 * @param message
+	 */
+	public ScriptEvaluationException(final String message) {
+		super(message);
+	}
+
+	/**
+	 * 
+	 * @param cause
+	 */
+	public ScriptEvaluationException(final Throwable cause) {
+		super(cause);
+	}
+
+	/**
+	 * 
+	 * @param message
+	 * @param cause
+	 */
+	public ScriptEvaluationException(final String message, final Throwable cause) {
+		super(message, cause);
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/ScriptFunction.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.util.List;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public interface ScriptFunction {
+	Value apply(List args) throws ScriptEvaluationException;
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/ScriptReferencedVariable.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import org.apache.log4j.Logger;
+
+
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class ScriptReferencedVariable implements Variable {
+	private final Logger logger = Logger.getLogger(ScriptReferencedVariable.class);
+	private final String name;
+	private Object value = null;
+	private Value initValue = null;
+	private boolean isHidden = false;
+	private String description = null;
+	
+	public ScriptReferencedVariable(final String name) {
+		this.name = name;
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#getDescription()
+	 */
+	public String getDescription() { return description; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#getInitialValue()
+	 */
+	public Value getInitialValue() { return initValue; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#getName()
+	 */
+	public String getName() { return name; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#getValue()
+	 */
+	public Object getValue() { return value; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#isHidden()
+	 */
+	public boolean isHidden() { return isHidden; }
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#setDescription(java.lang.String)
+	 */
+	public void setDescription(final String description) {
+		this.description = description;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#setInitialValue(org.nrg.ecat.edit.Value)
+	 */
+	public void setInitialValue(final Value value) throws MultipleInitializationException{
+		if (null == initValue) {
+			logger.trace(this + " initial value set to " + value);
+			initValue = value;
+		} else {
+			throw new MultipleInitializationException(this, value);
+		}
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#setIsHidden(boolean)
+	 */
+	public void setIsHidden(final boolean isHidden) {
+		this.isHidden = isHidden;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Variable#setValue(java.lang.Object)
+	 */
+	public void setValue(final Object value) {
+		logger.trace(this + " value set to " + value);
+		this.value = value;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		final StringBuffer sb = new StringBuffer(super.toString());
+		return sb.append(" (").append(name).append(")").toString();
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/Value.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public interface Value {
+	Set getVariables();
+	Object on(Map m) throws ScriptEvaluationException;
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/Variable.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public interface Variable {
+	String getDescription();
+	String getName();
+	Value getInitialValue();
+	Object getValue();
+	boolean isHidden();
+	void setDescription(String description);
+	void setInitialValue(Value value) throws MultipleInitializationException;
+	void setIsHidden(boolean isHidden);
+	void setValue(Object value);
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/VariableValue.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public class VariableValue implements Value {
+	private final Variable variable;
+	
+	public VariableValue(final Variable variable) {
+		this.variable = variable;
+	}
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#getVariables()
+	 */
+	public Set getVariables() {
+		return Collections.singleton(variable);
+	}
+
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.Value#on(java.util.Map)
+	 */
+	public Object on(Map m) throws ScriptEvaluationException {
+		final Object v = variable.getValue();
+		if (null == v) {
+			final Value initial = variable.getInitialValue();
+			return null == initial ? null : initial.on(m);
+		} else {
+			return v;
+		}
+	}
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/fn/Format.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit.fn;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.nrg.ecat.edit.MessageFormatValue;
+import org.nrg.ecat.edit.ScriptEvaluationException;
+import org.nrg.ecat.edit.ScriptFunction;
+import org.nrg.ecat.edit.Value;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class Format implements ScriptFunction {
+	public static final String name = "format";
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.ScriptFunction#apply(java.util.List)
+	 */
+	public Value apply(final List args) throws ScriptEvaluationException {
+		final Iterator i = args.iterator();
+		final Value pattern = (Value)i.next();
+		final List elements = new ArrayList(args.size() - 1);
+		while (i.hasNext()) {
+			elements.add(i.next());
+		}
+		
+		return new MessageFormatValue(pattern, elements);
+	}
+
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/fn/Lowercase.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit.fn;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.nrg.ecat.edit.ScriptEvaluationException;
+import org.nrg.ecat.edit.ScriptFunction;
+import org.nrg.ecat.edit.Value;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class Lowercase implements ScriptFunction {
+	public static final String name = "lowercase";
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.ScriptFunction#apply(java.util.List)
+	 */
+	public Value apply(List args) throws ScriptEvaluationException {
+		final Value v;
+		try {
+			v = (Value)args.get(0);
+		} catch (IndexOutOfBoundsException e) {
+			throw new ScriptEvaluationException(name + " requires one argument");
+		}
+		return new Value() {
+			public Object on(Map m) throws ScriptEvaluationException {
+				return toLower(v.on(m));
+			}
+			
+			public Set getVariables() { return v.getVariables(); }
+		};
+	}
+	
+	private static String toLower(final Object o) {
+		return null == o ? null : o.toString().toLowerCase();
+	}
+
+}

File ecat-edit/src/main/java/org/nrg/ecat/edit/fn/Substring.java

+/**
+ * Copyright (c) 2010 Washington University
+ */
+package org.nrg.ecat.edit.fn;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.nrg.ecat.edit.IntegerValue;
+import org.nrg.ecat.edit.ScriptEvaluationException;
+import org.nrg.ecat.edit.ScriptFunction;
+import org.nrg.ecat.edit.Value;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class Substring implements ScriptFunction {
+	public static final String name = "substring";
+	
+	/* (non-Javadoc)
+	 * @see org.nrg.ecat.edit.ScriptFunction#apply(java.util.List)
+	 */
+	public Value apply(final List args) throws ScriptEvaluationException {
+		final Value s = (Value)args.get(0);
+		final int start;
+		final int end;
+		try {
+			start = ((IntegerValue)args.get(1)).getValue();
+			end = ((IntegerValue)args.get(2)).getValue();
+		} catch (ClassCastException e) {
+			throw new ScriptEvaluationException("substring[ string, start(integer), end(integer)]");
+		}
+		return new Value() {
+			public Set getVariables() { return s.getVariables(); }
+			public Object on(Map m) throws ScriptEvaluationException {
+				final Object o = s.on(m);
+				return null == o ? null : o.toString().substring(start, end);
+			}
+		};
+	}
+}

File ecat-edit/src/main/java/org/nrg/util/GraphUtils.java

+package org.nrg.util;
+/**
+ * Copyright 2009 Washington University
+ */
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ *
+ */
+public final class GraphUtils {
+    private GraphUtils() {}	// prevent instantiation
+        
+    /**
+     * Indicates that an algorithm that requires a directed acyclic graph (DAG)
+     * was instead provided a cyclic graph.
+     * @author Kevin A. Archie <karchie@npg.wustl.edu>
+     *
+     */
+    public static class CyclicGraphException extends IllegalArgumentException {
+	private static final long serialVersionUID = 1L;
+	private final Object partial;
+	
+	CyclicGraphException(final String msg, final Object partial) {
+	    super(msg);
+	    this.partial = partial;
+	}
+	
+	CyclicGraphException(final Object partial) {
+	    super();
+	    this.partial = partial;
+	}
+	
+	/**
+	 * Retrieves partial result from algorithm, if one is available.
+	 * @return partial result (type determined by throwing algorithm)
+	 */
+	public Object getPartialResult() { return partial; }
+    }
+    
+
+    /**
+     * Use Kahn's algorithm for topological sort
+     * @param graph Map X -> Collection<X> ; each entry maps from a node to its incoming edges
+     * @return topologically sorted X
+     * @throws CyclicGraphException if graph is cyclic
+     */
+    public static List topologicalSort(final Map graph) throws CyclicGraphException {
+	final Map g = new LinkedHashMap(graph);
+	final List sorted = new ArrayList(g.size());
+	
+	// Extract all nodes with no incoming edges
+	final Set s = new LinkedHashSet();
+	for (final Iterator i = graph.entrySet().iterator(); i.hasNext(); ) {
+	    final Map.Entry me = (Map.Entry)i.next();
+	    if (((Collection)me.getValue()).isEmpty()) {
+		final Object n = me.getKey();
+		s.add(n);
+		g.remove(n);
+	    }
+	}
+	
+	while (!s.isEmpty()) {
+	    final Object n = s.iterator().next();
+	    s.remove(n);
+	    sorted.add(n);
+	    for (final Iterator i = g.entrySet().iterator(); i.hasNext(); ) {
+		final Map.Entry me = (Map.Entry)i.next();
+		final Collection incoming = (Collection)me.getValue();
+		incoming.remove(n);
+		if (incoming.isEmpty()) {
+		    s.add(me.getKey());
+		    i.remove();
+		}
+	    }
+	}
+	
+	if (g.isEmpty()) {
+	    return sorted;
+	} else {
+	    throw new CyclicGraphException("some nodes are in cyclic graph: " + g.keySet(), sorted);
+	}
+    }
+}
   </developers>
   <modules>
     <module>ecat-io</module>
+    <module>ecat-edit</module>
   </modules>
 </project>