Commits

Kevin A. Archie  committed 258a6ef

big refactoring, including move to generics, to fix tag accumulation bug

  • Participants
  • Parent commits e8ba043

Comments (0)

Files changed (66)

   <modelVersion>4.0.0</modelVersion>
   <issueManagement>
   	<system>FogBugz</system>
-  	<url>http://nrg.wustl.edu/fogbugz</url>
+  	<url>http://bugs.nrg.wustl.edu</url>
   </issueManagement>
-  <ciManagement>
-  	<system>Hudson</system>
-  	<url>http://nrg.wustl.edu/hudson</url>
-  </ciManagement>
   <scm>
   	<url>http://hg.xnat.org/dicomedit</url>
   </scm>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.3.2</version>
         <configuration>
-          <source>1.4</source>
-          <target>1.4</target>
+          <source>1.5</source>
+          <target>1.5</target>
         </configuration>
       </plugin>
       <plugin>
   <groupId>org.nrg</groupId>
   <artifactId>DicomEdit</artifactId>
   <packaging>jar</packaging>
-  <version>1.5.3</version>
+  <version>2.0.0b1</version>
   <name>DicomEdit</name>
   <description>Implementation of a language for modifying DICOM metadata</description>
   <url>http://nrg.wustl.edu</url>
 	  </exclusion>
 	</exclusions>     
     </dependency>
+    <dependency>
+    	<groupId>com.google.guava</groupId>
+    	<artifactId>guava</artifactId>
+    	<version>r09</version>
+    </dependency>
   </dependencies>
 </project>

File src/main/antlr3/org/nrg/dcm/edit/EditDCMTreeParser.g

 	
 	import java.io.IOException;
 	import java.util.Collections;
+	import java.util.ArrayList;
+	import java.util.List;
 	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();
+	private final Map<String,Variable> variables = new TreeMap<String,Variable>();
+	private final Map<String,ScriptFunction> functions = new HashMap<String,ScriptFunction>();
 	
   /**
    * Adapted from JSON interpreter string extractor by Richard Clark
 		}
 	}
 	
-	public final Map getVariables() {
+	public final Map<String,Variable> getVariables() {
 		return Collections.unmodifiableMap(variables);
 	}
 	
 	    System.out.println("empty script");
 	  } else {
 	    final EditDCMTreeParser treeParser = new EditDCMTreeParser(new CommonTreeNodeStream(ast));
-	    final StatementList statements = treeParser.script();
+	    final List<Statement> statements = treeParser.script();
 	    
 	    System.out.println(statements);
 	    
    **************************************************************************/
 }
 
-script returns [StatementList sl]
+script returns [List<Statement> sl]
 	@init {
-		$sl = new StatementArrayList();
+		$sl = new ArrayList<Statement>();
 	}
 	:	(s=statement { if (null != s) $sl.add(s); })*;
 

File src/main/java/org/nrg/dcm/edit/AbstractAction.java

-/**
- * Copyright (c) 2006-2009 Washington University
- */
-package org.nrg.dcm.edit;
-
-/**
- * Skeleton class for an action on a specific DICOM attribute
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
- */
-abstract class AbstractAction implements Action {
-    private final int tag;
-
-    /**
-     * @param tag specifies the attribute to be affected
-     */
-    AbstractAction(int tag) { this.tag = tag; }
-    public final int getTag() { return tag; }
-}

File src/main/java/org/nrg/dcm/edit/AbstractIndexedLabelFunction.java

 /**
- * Copyright (c) 2010 Washington University
+ * Copyright (c) 2010,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@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 {
+	private final Value getFormat(final List<Value> values) throws ScriptEvaluationException {
 		try {
-			return (Value)values.get(0);
+			return values.get(0);
 		} catch (IndexOutOfBoundsException e) {
 			try {
 				throw new ScriptEvaluationException(this.getClass().getField("name").get(null)
 	/* (non-Javadoc)
 	 * @see org.nrg.dcm.edit.ScriptFunction#apply(java.util.List)
 	 */
-	public Value apply(final List args) throws ScriptEvaluationException {
+	public Value apply(final List<Value> args) throws ScriptEvaluationException {
 		final Value format = getFormat(args);
 		return new Value() {
 			private NumberFormat buildFormatter(final int len) {
-				final StringBuffer sb = new StringBuffer();
+				final StringBuilder sb = new StringBuilder();
 				for (int i = 0; i < len; i++) {
 					sb.append("0");
 				}
 				}				
 			}
 			
-			public String on(final Map m) throws ScriptEvaluationException {
+			public String on(final Map<Integer,String> m) throws ScriptEvaluationException {
 				return valueFor(format.on(m));
 			}
 			
-			public String on(DicomObject o) throws ScriptEvaluationException {
+			public String on(final DicomObject o) throws ScriptEvaluationException {
 				return valueFor(format.on(o));
 			}
 			
-			public Set getVariables() { return format.getVariables(); }
+			public Set<Variable> getVariables() { return format.getVariables(); }
 			
-			public Set getTags() { return format.getTags(); }
+			public SortedSet<Integer> getTags() { return format.getTags(); }
 		};
 	}
 

File src/main/java/org/nrg/dcm/edit/AbstractOperation.java

 /**
- * Copyright (c) 2006-2009 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public abstract class AbstractOperation implements Operation {
     private final String name;
-    private final int tag;
 
-    AbstractOperation(final String name, final int tag) {
-	this.name = name;
-	this.tag = tag;
+    AbstractOperation(final String name) {
+        this.name = name;
     }
 
     /*
      * @see org.nrg.dcm.edit.Operation#getName()
      */
     public String getName() { return name; }
-    
-    /*
-     * (non-Javadoc)
-     * @see org.nrg.dcm.edit.Operation#getTag()
-     */
-    final public int getTag() { return tag; }
 }

File src/main/java/org/nrg/dcm/edit/AbstractValueConstraint.java

 /**
- * Copyright (c) 2006-2009 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
+import java.util.SortedSet;
+
 import org.dcm4che2.data.DicomObject;
 import org.dcm4che2.data.VR;
 import org.dcm4che2.util.StringUtils;
 import org.dcm4che2.util.TagUtils;
 
+import com.google.common.collect.ImmutableSortedSet;
+
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 abstract class AbstractValueConstraint implements ConstraintMatch {
-	private final int tag;
-	private final Value toMatch;
+    private final int tag;
+    private final Value toMatch;
 
-	AbstractValueConstraint(final int tag, final Value toMatch) {
-		this.tag = tag;
-		this.toMatch = toMatch;
-	}
+    AbstractValueConstraint(final int tag, final Value toMatch) {
+        this.tag = tag;
+        this.toMatch = toMatch;
+    }
 
-	abstract protected boolean matches(String pattern, String value);
+    abstract protected boolean matches(String pattern, String value);
 
-	final public boolean matches(final DicomObject o) throws ScriptEvaluationException {
-		if (o.contains(tag)) {
-			final VR vr = o.vrOf(tag);
-			final String value;
-			if (VR.SQ == vr) { 
-				throw new RuntimeException("can't use SQ type attribute for constraint");
-			} else if (VR.UN == vr) {
-				value = o.getString(tag);
-			} else {
-				value = StringUtils.join(o.getStrings(tag), '\\');
-			}
-			return matches(toMatch.on(o), value);
-		} else
-			return false;
-	}
+    public SortedSet<Integer> getTags() {
+        return ImmutableSortedSet.of(tag);
+    }
 
-	public String toString(final String operation) {
-		return "Constraint: " + TagUtils.toString(tag) + " " + operation + " " + toMatch;
-	}
+    final public boolean matches(final DicomObject o) throws ScriptEvaluationException {
+        if (o.contains(tag)) {
+            final VR vr = o.vrOf(tag);
+            final String value;
+            if (VR.SQ == vr) { 
+                throw new RuntimeException("can't use SQ type attribute for constraint");
+            } else if (VR.UN == vr) {
+                value = o.getString(tag);
+            } else {
+                value = StringUtils.join(o.getStrings(tag), '\\');
+            }
+            return matches(toMatch.on(o), value);
+        } else
+            return false;
+    }
+
+    public String toString(final String operation) {
+        return "Constraint: " + TagUtils.toString(tag) + " " + operation + " " + toMatch;
+    }
 }

File src/main/java/org/nrg/dcm/edit/Action.java

 /**
- * Copyright (c) 2006-2010 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
+import java.util.SortedSet;
+
 /**
- * Represents an action to be performed on a specific DICOM attribute
+ * Represents an action to be performed on specific DICOM attributes
  * @author Kevin A. Archie <karchie@wustl.edu>
  */
 public interface Action {
     /**
-     * @return the tag of the affected attribute
+     * @return the tags of the affected attributes
      */
-    public int getTag();
+    SortedSet<Integer> getTags();
 
     /**
      * Performs the associated action.
      */
-    public void apply() throws ScriptEvaluationException;
+    void apply() throws ScriptEvaluationException;
 }

File src/main/java/org/nrg/dcm/edit/Assignment.java

 /**
- * Copyright (c) 2006-2010 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.text.MessageFormat;
 import java.util.Map;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 import org.dcm4che2.util.TagUtils;
 
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSortedSet;
+
 
 /**
  * @author Kevin A. Archie <karchie@wustl.edu>
  */
 public class Assignment extends AbstractOperation {
-	private final Value value;
+    private final int tag;
+    private final Value value;
 
-	public Assignment(final int tag, final Value value) {
-		super("Assign", tag);
-		this.value = value;
-	}
+    public Assignment(final int tag, final Value value) {
+        super("Assign to " + TagUtils.toString(tag));
+        this.tag = tag;
+        this.value = value;
+    }
 
-	public Assignment(final Integer tag, final Value value) {
-		this(tag.intValue(), value);
-	}
+    public Assignment(final Integer tag, final Value value) {
+        this(tag.intValue(), value);
+    }
 
-	public Assignment(final int tag, final String svalue) {
-		this(tag, new ConstantValue(svalue));
-	}
+    public Assignment(final int tag, final String svalue) {
+        this(tag, new ConstantValue(svalue));
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#equals(java.lang.Object)
-	 */
-	public boolean equals(Object o) {
-		if (o instanceof Assignment) {
-			final Assignment oa = (Assignment)o;
-			return getTag() == oa.getTag() && value.equals(oa.value);
-		} else {
-			return false;
-		}
-	}
+    public SortedSet<Integer> getAffectedTags() {
+        return ImmutableSortedSet.of(tag);
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#hashCode()
-	 */
-	public int hashCode() {
-		return this.getTag() + value.hashCode();
-	}
+    public SortedSet<Integer> getRequiredTags() {
+        return value.getTags();
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Operation#makeAction(org.dcm4che2.data.DicomObject)
-	 */
-	public Action makeAction(final DicomObject o) throws AttributeException {
-		return new AbstractAction(getTag()) {
-			public void apply() throws ScriptEvaluationException {
-				final int tag = getTag();
-				try {
-					o.putString(new int[]{tag}, o.vrOf(tag), value.on(o));
-				} catch (UnsupportedOperationException e) {
-					throw new ScriptEvaluationException("Unable to set attribute "
-							+ TagUtils.toString(tag) + " to \"" + value.on(o) + "\"", e);
-				}
-			}
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object o) {
+        if (o instanceof Assignment) {
+            final Assignment oa = (Assignment)o;
+            return tag == oa.tag && value.equals(oa.value);
+        } else {
+            return false;
+        }
+    }
 
-			public String toString() {
-				return MessageFormat.format("{0}: {1} := {2}",
-						new Object[]{getName(), TagUtils.toString(getTag()), value});
-			}
-		};
-	}
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return Objects.hashCode(Integer.valueOf(tag), value);
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Operation#apply(java.util.Map)
-	 */
-	public String apply(final Map vals) throws ScriptEvaluationException {
-		return value.on(vals);
-	}
+    /*
+     * (non-Javadoc)
+     * @see org.nrg.dcm.edit.Operation#makeAction(org.dcm4che2.data.DicomObject)
+     */
+    public Action makeAction(final DicomObject o) throws AttributeException {
+        return new Action() {
+            public void apply() throws ScriptEvaluationException {
+                try {
+                    o.putString(new int[]{tag}, o.vrOf(tag), value.on(o));
+                } catch (UnsupportedOperationException e) {
+                    throw new ScriptEvaluationException("Unable to set attribute "
+                            + TagUtils.toString(tag) + " to \"" + value.on(o) + "\"", e);
+                }
+            }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#toString()
-	 */
-	public String toString() {
-		final StringBuffer sb = new StringBuffer(getName());
-		sb.append(" ");
-		sb.append(TagUtils.toString(getTag()));
-		sb.append(" := ").append(value);
-		return sb.toString();
-	}
+            public SortedSet<Integer> getTags() {
+                return ImmutableSortedSet.of(tag);
+            }
+
+            public String toString() {
+                return MessageFormat.format("{0}: {1} := {2}",
+                        new Object[]{getName(), TagUtils.toString(tag), value});
+            }
+        };
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see org.nrg.dcm.edit.Operation#apply(java.util.Map)
+     */
+    public String apply(final Map<Integer,String> vals) throws ScriptEvaluationException {
+        return value.on(vals);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        final StringBuffer sb = new StringBuffer(getName());
+        sb.append(" ");
+        sb.append(TagUtils.toString(tag));
+        sb.append(" := ").append(value);
+        return sb.toString();
+    }
 }

File src/main/java/org/nrg/dcm/edit/AttributeException.java

 /*
- * Copyright (c) 2009 Washington University
+ * Copyright (c) 2009,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public abstract class AttributeException extends Exception {
-    private final static long serialVersionUID = 1L;
-    
-    AttributeException(final String s) { super(s); }
+    private static final long serialVersionUID = -5568999633339020555L;
+
+    public AttributeException(final String s) { super(s); }
 }

File src/main/java/org/nrg/dcm/edit/BindingContext.java

 /**
- * Copyright (c) 2009 Washington University
+ * Copyright (c) 2009,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
-import java.util.HashMap;
 import java.util.Map;
 
+import com.google.common.collect.Maps;
 
+/**
+ * @author Kevin A. Archie <karchie@wustl.edu>
+ *
+ */
 public class BindingContext {
-    private final Map defined = new HashMap();
+    private final Map<String,Object> defined = Maps.newHashMap();
 
     final void bind(final String label, final Object value) throws AlreadyBoundLabelException {
-	final String canonical = label.toLowerCase();
-	if (defined.containsKey(canonical)) {
-	    throw new AlreadyBoundLabelException(canonical);
-	}
-	defined.put(canonical, value);
+        final String canonical = label.toLowerCase();
+        if (defined.containsKey(canonical)) {
+            throw new AlreadyBoundLabelException(canonical);
+        }
+        defined.put(canonical, value);
     }
 
     final Object getValue(final String label) {
-	final String canonical = label.toLowerCase();
-	if (defined.containsKey(canonical)) {
-	return defined.get(canonical);
-	} else {
-	    throw new UnboundLabelException(label);
-	}
+        final String canonical = label.toLowerCase();
+        if (defined.containsKey(canonical)) {
+            return defined.get(canonical);
+        } else {
+            throw new UnboundLabelException(label);
+        }
     }
-    
-    
+
+
     public static class UnboundLabelException extends RuntimeException {
-	private final static long serialVersionUID = 1L;
-	
-	UnboundLabelException(final String s) {
-	    super("Label not defined: " + s);
-	}
+        private final static long serialVersionUID = 1L;
+
+        UnboundLabelException(final String s) {
+            super("Label not defined: " + s);
+        }
     }
-    
+
     public static class AlreadyBoundLabelException extends RuntimeException {
-	private final static long serialVersionUID = 1L;
-	
-	AlreadyBoundLabelException(final String s) {
-	    super("Label already bound: " + s);
-	}
+        private final static long serialVersionUID = 1L;
+
+        AlreadyBoundLabelException(final String s) {
+            super("Label already bound: " + s);
+        }
     }
 }

File src/main/java/org/nrg/dcm/edit/ConstantValue.java

 /**
- * Copyright (c) 2009,2010 Washington University
+ * Copyright (c) 2009,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
-import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
+import com.google.common.collect.ImmutableSortedSet;
+
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public final class ConstantValue implements Value {
     private final String value;
-    
+
     public ConstantValue(final String value) {
-	this.value = value;
+        this.value = value;
     }
 
     /*
      * (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#getTags()
      */
-    public Set getTags() { return Collections.EMPTY_SET; }
+    public SortedSet<Integer> getTags() { return ImmutableSortedSet.of(); }
 
     /*
      * (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#getVariables()
      */
-    public Set getVariables() { return Collections.EMPTY_SET; }
-    
+    public Set<Variable> getVariables() { return ImmutableSortedSet.of(); }
+
     /* (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#on(org.dcm4che2.data.DicomObject)
      */
     public String on(final DicomObject o) {
-	return value;
+        return value;
     }
 
     /* (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#on(java.util.Map)
      */
-    public String on(final Map m) {
-	return value;
+    public String on(final Map<Integer,String> m) {
+        return value;
     }
-    
+
     /*
      * (non-Javadoc)
      * @see java.lang.Object#toString()
      */
     public String toString() {
-    	final StringBuffer sb = new StringBuffer(super.toString());
-    	sb.append(" (").append(value).append(")");
-    	return sb.toString();
+        final StringBuilder sb = new StringBuilder(super.toString());
+        sb.append(" (").append(value).append(")");
+        return sb.toString();
     }
 }

File src/main/java/org/nrg/dcm/edit/Constraint.java

 /**
- * Copyright (c) 2006-2009 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
-import java.util.HashSet;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
+import com.google.common.collect.Sets;
+
 /**
  * Represents a constraint on an Operation.
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  */
 public class Constraint {
-	private static final Collection emptyCollection = Collections.unmodifiableCollection(new ArrayList(0));
-	private final ConstraintMatch m;
-	private final Set files;
+    private static final Iterable<File> empty = Collections.emptyList();
+    private final ConstraintMatch m;
+    private final Set<File> files;
 
-	public Constraint(final ConstraintMatch m) {
-		this(m, emptyCollection);
-	}
+    public Constraint(final ConstraintMatch m) {
+        this(m, empty);
+    }
 
-	public Constraint(final ConstraintMatch m, final File f) {
-		this(m, Collections.singletonList(f));
-	}
+    public Constraint(final ConstraintMatch m, final File f) {
+        this(m, Collections.singletonList(f));
+    }
 
-	public Constraint(final ConstraintMatch m, final Collection fs) {
-		this.m = m;
-		files = new HashSet(fs);
-		if (null == m && files.isEmpty()) {
-			throw new IllegalArgumentException("Either value constraint or file list must be provided");
-		}
-	}
+    public Constraint(final ConstraintMatch m, final Iterable<File> fs) {
+        this.m = m;
+        files = Sets.newHashSet(fs);
+        if (null == m && files.isEmpty()) {
+            throw new IllegalArgumentException("Either value constraint or file list must be provided");
+        }
+    }
 
-	boolean matches(final File f, final DicomObject o) throws ScriptEvaluationException {
-		assert m != null || !files.isEmpty();
-		if (m != null && !m.matches(o)) {
-			return false;
-		}
-		return files.isEmpty() || files.contains(f);
-	}
+    boolean matches(final File f, final DicomObject o) throws ScriptEvaluationException {
+        assert m != null || !files.isEmpty();
+        if (m != null && !m.matches(o)) {
+            return false;
+        }
+        return files.isEmpty() || files.contains(f);
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#toString()
-	 */
-	public String toString() { return m.toString(); }
+    public SortedSet<Integer> getTags() {
+        return m.getTags();
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() { return m.toString(); }
 }

File src/main/java/org/nrg/dcm/edit/ConstraintConjunction.java

 /**
- * Copyright (c) 2008-2009 Washington University
+ * Copyright (c) 2008-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public class ConstraintConjunction implements ConstraintMatch {
-	private final Collection predicates;
+    private final Iterable<ConstraintMatch> predicates;
 
-	public ConstraintConjunction(final ConstraintMatch predicate) {
-		this(Collections.singletonList(predicate));
-	}
+    public ConstraintConjunction(final ConstraintMatch predicate) {
+        this(Collections.singletonList(predicate));
+    }
 
-	public ConstraintConjunction(final Collection predicates) {
-		this.predicates = new ArrayList(predicates);
-	}
+    public ConstraintConjunction(final Iterable<ConstraintMatch> predicates) {
+        this.predicates = Lists.newArrayList(predicates);
+    }
 
-	/* (non-Javadoc)
-	 * @see org.nrg.dcm.edit.ConstraintMatch#matches(org.dcm4che2.data.DicomObject)
-	 */
-	public boolean matches(final DicomObject o) throws ScriptEvaluationException {
-		for (final Iterator pi = predicates.iterator(); pi.hasNext(); ) {
-			final ConstraintMatch predicate = (ConstraintMatch)pi.next();
-			if (!predicate.matches(o)) {
-				return false;
-			}
-		}
-		return true;
-	}
+    public SortedSet<Integer> getTags() {
+        final SortedSet<Integer> tags = Sets.newTreeSet();
+        for (final ConstraintMatch p : predicates) {
+            tags.addAll(p.getTags());   
+        }
+        return tags;
+    }
 
-	public String toString() {
-		final StringBuffer sb = new StringBuffer("ConstraintConjunction: ");
-		final Iterator i = predicates.iterator();
-		if (i.hasNext()) {
-			sb.append(i.next());
-		} else {
-			sb.append("[TRUE]");
-		}
-		while (i.hasNext()) {
-			sb.append(" AND ");
-			sb.append(i.next());
-		}
-		return sb.toString();
-	}
+    /* (non-Javadoc)
+     * @see org.nrg.dcm.edit.ConstraintMatch#matches(org.dcm4che2.data.DicomObject)
+     */
+    public boolean matches(final DicomObject o) throws ScriptEvaluationException {
+        for (final ConstraintMatch predicate : predicates) {
+            if (!predicate.matches(o)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("ConstraintConjunction: ");
+        if (Iterables.isEmpty(predicates)) {
+            sb.append("[TRUE]");
+        } else {
+            Joiner.on(" AND ").appendTo(sb, predicates);
+        }
+        return sb.toString();
+    }
 
 }

File src/main/java/org/nrg/dcm/edit/ConstraintMatch.java

 /**
- * Copyright (c) 2008-2009 Washington University
+ * Copyright (c) 2008-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
+import java.util.SortedSet;
+
 import org.dcm4che2.data.DicomObject;
 
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 interface ConstraintMatch {
+    SortedSet<Integer> getTags();
     boolean matches(DicomObject o) throws ScriptEvaluationException;
 }

File src/main/java/org/nrg/dcm/edit/Deletion.java

 /**
- * Copyright (c) 2009 Washington University
+ * Copyright (c) 2009,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.util.Map;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 import org.dcm4che2.util.TagUtils;
 
+import com.google.common.collect.ImmutableSortedSet;
+
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public class Deletion extends AbstractOperation {
-    public Deletion(final int tag) { super("Delete", tag); }
-    
-    public Deletion(final Integer tag) { this(tag.intValue()); }
+    private final int tag;
+
+    public Deletion(final int tag) {
+        super("Delete");
+        this.tag = tag;
+    }
 
     public Action makeAction(final DicomObject o) throws AttributeException {
-	return new AbstractAction(getTag()) {
-	    public void apply() { o.remove(getTag()); }
-	    public String toString() {
-		return getName() + " " + TagUtils.toString(getTag());
-	    }
-	};
+        return new Action() {
+            public void apply() { o.remove(tag); }
+
+            public SortedSet<Integer> getTags() {
+                return ImmutableSortedSet.of(tag);
+            }
+
+            public String toString() {
+                return getName() + " " + TagUtils.toString(tag);
+            }
+        };
     }
 
-    public String apply(Map vals) { return null; }
+    public String apply(Map<Integer,String> vals) { return null; }
 
+    public SortedSet<Integer> getAffectedTags() {
+        return ImmutableSortedSet.of(tag);
+    }
+
+    public SortedSet<Integer> getRequiredTags() {
+        return ImmutableSortedSet.of();
+    }
     /*
      * (non-Javadoc)
      * @see java.lang.Object#toString()
      */
     public String toString() {
-	return getName() + TagUtils.toString(getTag());
+        return "Delete " + TagUtils.toString(tag);
     }
 }

File src/main/java/org/nrg/dcm/edit/DicomUtils.java

 import java.net.URL;
 import java.util.Calendar;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.TimeZone;
 import java.util.regex.Pattern;
 import org.dcm4che2.io.StopTagInputHandler;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+
 
 /**
  * @author Kevin A. Archie <karchie@wustl.edu>
         return join(new StringBuffer(), array, separator).toString();
     }
 
-    private static StringBuffer join(final StringBuffer sb, final Object[] array, final String separator) {
-        if (array.length > 0) {
-            sb.append(array[0]);
-            for (int i = 1; i < array.length; i++) {
-                sb.append(separator);
-                sb.append(array[i]);
-            }
-        }
-        return sb;
-    }
-
-    public static String join(final Object[] array, final String separator) {
-        return join(new StringBuffer(), array, separator).toString();
-    }
-
-
     private static interface Converter {
         String convert(DicomObject o, DicomElement e) throws AttributeVRMismatchException;
     }
 
-    private final static Map conversions = new HashMap();
-    static {
-        conversions.put(VR.SQ, new Converter() {
+    private final static Map<VR,Converter> conversions = 
+        ImmutableMap.of(VR.SQ, new Converter() {
             public String convert(final DicomObject o, final DicomElement e)
             throws AttributeVRMismatchException {
                 throw new AttributeVRMismatchException(e.tag(), e.vr());
             }
-        });
-
-        conversions.put(VR.UN, new Converter() {
+        },
+        VR.UN, new Converter() {
             public String convert(final DicomObject o, final DicomElement e) {
                 return e.getString(o.getSpecificCharacterSet(), false);
             }
-        });
-
-        conversions.put(VR.AT, new Converter() {
+        },
+        VR.AT, new Converter() {
             public String convert(final DicomObject o, final DicomElement e) {
                 return join(e.getInts(false), "\\");
             }
-        });
-
-        conversions.put(VR.OB, new Converter() {
+        },
+        VR.OB, new Converter() {
             public String convert(final DicomObject o, final DicomElement e) {
                 return VR.OB.toString(e.getBytes(), o.bigEndian(), o.getSpecificCharacterSet());
             }
         });
-    }
 
 
     public static String getString(final DicomObject o, final int tag)
             // all other data types can be treated as simple strings, maybe with
             // multiple values separated by backslashes.  Join these.
             try {
-                return join(de.getStrings(o.getSpecificCharacterSet(), false), "\\");
+                return Joiner.on("\\").join(de.getStrings(o.getSpecificCharacterSet(), false));
             } catch (UnsupportedOperationException e) {
                 throw new AttributeVRMismatchException(tag, de.vr());
             }

File src/main/java/org/nrg/dcm/edit/Echo.java

 /**
- * Copyright (c) 2006-2009 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.util.Map;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
+import com.google.common.collect.ImmutableSortedSet;
+
 
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  */
 public final class Echo implements Operation {
+    private static final SortedSet<Integer> EMPTY = ImmutableSortedSet.of();
+
     private final Value v;
 
     public Echo(final String message) {
-	this(new ConstantValue(message));
+        this(new ConstantValue(message));
     }
-    
+
     public Echo(final Value v) {
-	this.v = v;
+        this.v = v;
     }
-    
+
     /* (non-Javadoc)
      * @see org.nrg.dcm.edit.Operation#makeAction(org.dcm4che2.data.DicomObject)
      */
     public Action makeAction(final DicomObject o) {
-	String m;
-	try {
-	    m = v.on(o);
-	} catch (ScriptEvaluationException e) {
-	    m = "[uninterpretable: " + v + "]";
-	}
-	final String message = m;
+        String m;
+        try {
+            m = v.on(o);
+        } catch (ScriptEvaluationException e) {
+            m = "[uninterpretable: " + v + "]";
+        }
+        final String message = m;
 
-	return new AbstractAction(0) {
-	    public void apply() { System.out.print(message); }
-	    public String toString() { return getName() + ": " + message; }
-	};
+        return new Action() {
+            public void apply() { System.out.print(message); }
+            public String toString() { return getName() + ": " + message; }
+            public SortedSet<Integer> getTags() { return EMPTY; }
+        };
     }
 
     /*
      * (non-Javadoc)
      * @see org.nrg.dcm.edit.Operation#apply(java.util.Map)
      */
-    public String apply(final Map vals) { return null; }
+    public String apply(final Map<Integer,String> vals) { return null; }
 
-    /*
-     * (non-Javadoc)
-     * @see org.nrg.dcm.edit.Operation#getTag()
-     */
-    public int getTag() { return -1; }
+    public SortedSet<Integer> getAffectedTags() { return EMPTY; }
+
+    public SortedSet<Integer> getRequiredTags() { 
+        return v.getTags();
+    }
 
     /*
      * (non-Javadoc)

File src/main/java/org/nrg/dcm/edit/GeneratedValue.java

  */
 package org.nrg.dcm.edit;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public class GeneratedValue implements Value {
     private final GeneratorScope scope;
     private final int tag;
     private final String label;
-    private final List params;
+    private final List<Value> params;
 
-    public GeneratedValue(final GeneratorScope scope, final int tag, final String label, final List params) {
+    public GeneratedValue(final GeneratorScope scope, final int tag, final String label, final List<Value> params) {
         this.scope = scope;
         this.tag = tag;
         this.label = label;
-        this.params = Collections.unmodifiableList(new ArrayList(params));
+        this.params = ImmutableList.copyOf(params);
     }
 
 
     /* (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#getTags()
      */
-    public Set getTags() {
-        final Set tags = new TreeSet();
-        tags.add(new Integer(tag));
-        for (final Iterator i = params.iterator(); i.hasNext(); ) {
-            tags.addAll(((Value)i.next()).getTags());
+    public SortedSet<Integer> getTags() {
+        final SortedSet<Integer> tags = Sets.newTreeSet();
+        for (final Value param : params) {
+            tags.addAll(param.getTags());
         }
         return tags;
     }
     /* (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#getVariables()
      */
-    public Set getVariables() {
-        final Set vars = new HashSet();
-        for (final Iterator i = params.iterator(); i.hasNext(); ) {
-            vars.addAll(((Value)i.next()).getVariables());
+    public Set<Variable> getVariables() {
+        final Set<Variable> vars = Sets.newLinkedHashSet();
+        for (final Value v : params) {
+            vars.addAll(v.getVariables());
         }
         return vars;
     }
      * @see org.nrg.dcm.edit.Value#on(org.dcm4che2.data.DicomObject)
      */
     public String on(final DicomObject o) throws ScriptEvaluationException {
-        final List values = new ArrayList(params.size());
-        for (final Iterator i = params.iterator(); i.hasNext(); ) {
-            values.add(((Value)i.next()).on(o));
+        final List<String> values = Lists.newArrayListWithExpectedSize(params.size());
+        for (final Value v : params) {
+            values.add(v.on(o));
         }
         return scope.getValue(label, tag, o.getString(tag), values);
     }
     /* (non-Javadoc)
      * @see org.nrg.dcm.edit.Value#on(java.util.Map)
      */
-    public String on(final Map m) throws ScriptEvaluationException {
-        final List values = new ArrayList(params.size());
-        for (final Iterator i = params.iterator(); i.hasNext(); ) {
-            values.add(((Value)i.next()).on(m));
+    public String on(final Map<Integer,String> m) throws ScriptEvaluationException {
+        final List<String> values = Lists.newArrayListWithExpectedSize(params.size());
+        for (final Value v : params) {
+            values.add(v.on(m));
         }
-        final Object v = m.get(new Integer(tag));
-        final String sv;
-        if (null == v) {
-            sv = null;
-        } else if (v instanceof Collection) {
-            sv = DicomUtils.join(((Collection) v).toArray(), ",");
-        } else {
-            sv = v.toString();
-        }
-        return scope.getValue(label, tag, sv, values);
+        return scope.getValue(label, tag, m.get(tag), values);
     }
 }

File src/main/java/org/nrg/dcm/edit/GeneratorScope.java

 /**
- * Copyright (c) 2010 Washington University
+ * Copyright (c) 2010,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Maps;
+
 
 /**
  * 
  *
  */
 public final class GeneratorScope {
-	private final Logger logger = LoggerFactory.getLogger(GeneratorScope.class);
-	private final Map maps = new HashMap();
+    private final Logger logger = LoggerFactory.getLogger(GeneratorScope.class);
+    private final Map<String,GeneratorCache> maps = Maps.newHashMap();
 
-	private interface GeneratorCache {
-		String getValue(final int tag, final List params, final String old) throws ScriptEvaluationException;
-	}
-	
-	private static final class TagCache implements GeneratorCache {
-		private final Map attrs = new HashMap();
-		private final ValueGenerator generator;
-		
-		TagCache(final ValueGenerator generator) {
-			this.generator = generator;
-		}
-	
-		public String getValue(final int tag, final List params, final String old)
-		throws ScriptEvaluationException {
-			final Integer key = new Integer(tag);
-			if (!attrs.containsKey(key)) {
-				attrs.put(key, new HashMap());
-			}
-			final Map valueMap = (Map)attrs.get(key);
-			final String v = (String)valueMap.get(old);
-			if (null == v) {
-				final String newv = generator.valueFor(old, params);
-				valueMap.put(old, newv);
-				return newv;
-			} else {
-				return v;
-			}
-		}
-	}
-	
-	public void setGenerator(final String label, final ValueGenerator generator) {
-		maps.put(label, new TagCache(generator));
-	}
+    private interface GeneratorCache {
+        String getValue(final int tag, final Iterable<String> params, final String old) throws ScriptEvaluationException;
+    }
 
-	/**
-	 * 
-	 * @param label
-	 * @param tag
-	 * @param o
-	 * @param old prior value 
-	 * @param values List of concrete (String) values
-	 * @return
-	 * @throws ScriptEvaluationException
-	 */
-	public String getValue(final String label, final int tag, final String old, final List values)
-	throws ScriptEvaluationException {
-		logger.trace("generating value for " + label + " " + values);
-		if (!maps.containsKey(label)) {
-			throw new ScriptEvaluationException("undefined generator " + label);
-		}
-		final GeneratorCache cache = (GeneratorCache)maps.get(label);
-		return cache.getValue(tag, values, old);
-	}
+    private static final class TagCache implements GeneratorCache {
+        private final Map<Integer,Map<String,String>> attrs = Maps.newHashMap();
+        private final ValueGenerator generator;
+
+        TagCache(final ValueGenerator generator) {
+            this.generator = generator;
+        }
+
+        public String getValue(final int tag, final Iterable<String> params, final String old)
+        throws ScriptEvaluationException {
+
+            final Integer key = new Integer(tag);
+            if (!attrs.containsKey(key)) {
+                attrs.put(key, new HashMap<String,String>());
+            }
+            final Map<String,String> valueMap = attrs.get(key);
+            final String v = valueMap.get(old);
+            if (null == v) {
+                final String newv = generator.valueFor(old, params);
+                valueMap.put(old, newv);
+                return newv;
+            } else {
+                return v;
+            }
+        }
+    }
+
+    public void setGenerator(final String label, final ValueGenerator generator) {
+        maps.put(label, new TagCache(generator));
+    }
+
+    /**
+     * 
+     * @param label
+     * @param tag
+     * @param old prior value 
+     * @param values concrete (String) values
+     * @return
+     * @throws ScriptEvaluationException
+     */
+    public String getValue(final String label, final int tag, final String old, final Iterable<String> values)
+    throws ScriptEvaluationException {
+        logger.trace("generating value for {} {}", label, values);
+        if (!maps.containsKey(label)) {
+            throw new ScriptEvaluationException("undefined generator " + label);
+        }
+        return maps.get(label).getValue(tag, values, old);
+    }
 }

File src/main/java/org/nrg/dcm/edit/IntegerValue.java

 /**
- * Copyright (c) 2010 Washington University
+ * Copyright (c) 2010,2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
+import com.google.common.collect.ImmutableSortedSet;
+
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@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.dcm.edit.Value#getTags()
-	 */
-	public Set getTags() { return Collections.EMPTY_SET; }
+    private final String sval;
+    private final int val;
 
-	/* (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#getVariables()
-	 */
-	public Set getVariables() { return Collections.EMPTY_SET; }
+    public IntegerValue(final String value) {
+        sval = value;
+        val = Integer.parseInt(value);
+    }
 
-	/* (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#on(org.dcm4che2.data.DicomObject)
-	 */
-	public String on(final DicomObject o) {
-		return sval;
-	}
+    /* (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#getTags()
+     */
+    public SortedSet<Integer> getTags() { return ImmutableSortedSet.of(); }
 
-	/* (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#on(java.util.Map)
-	 */
-	public String on(final Map m) {
-		return sval;
-	}
+    /* (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#getVariables()
+     */
+    public Set<Variable> getVariables() { return Collections.emptySet(); }
 
-	public int getValue() { return val; }
+    /* (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#on(org.dcm4che2.data.DicomObject)
+     */
+    public String on(final DicomObject o) {
+        return sval;
+    }
+
+    /* (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#on(java.util.Map)
+     */
+    public String on(final Map<Integer,String> m) {
+        return sval;
+    }
+
+    public int getValue() { return val; }
 }

File src/main/java/org/nrg/dcm/edit/MessageFormatValue.java

 /**
- * Copyright (c) 2009,2010 Washington University
+ * Copyright (c) 2009-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-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;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
 /**
  * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public final class MessageFormatValue implements Value {
-	private final Logger logger = LoggerFactory.getLogger(MessageFormatValue.class);
-	private final Value format;
-	private final List values;
-	private final Set tags;
-	private final Set variables;
+    private final Logger logger = LoggerFactory.getLogger(MessageFormatValue.class);
+    private final Value format;
+    private final List<Value> values;
+    private final SortedSet<Integer> tags;
+    private final Set<Variable> variables;
 
-	public MessageFormatValue(final Value format, final Collection values) {
-		this.format = format;
-		this.values = new ArrayList(values);
+    public MessageFormatValue(final Value format, final Iterable<Value> values) {
+        this.format = format;
+        this.values = Lists.newArrayList(values);
 
-		// Sort out the dependencies
-		final Set tt = new LinkedHashSet(), tv = new LinkedHashSet();
-		for (final Iterator i = this.values.iterator(); i.hasNext(); ) {
-			final Value v = (Value)i.next();
-			tt.addAll(v.getTags());
-			tv.addAll(v.getVariables());
-		}
-		this.tags = Collections.unmodifiableSet(tt);
-		this.variables = Collections.unmodifiableSet(tv);
-	}
+        // Sort out the dependencies
+        final Set<Integer> tags = Sets.newLinkedHashSet();
+        final Set<Variable> vars = Sets.newLinkedHashSet();
+        for (final Value value : values) {
+            tags.addAll(value.getTags());
+            vars.addAll(value.getVariables());
+        }
+        this.tags = ImmutableSortedSet.copyOf(tags);
+        this.variables = Collections.unmodifiableSet(vars);
+    }
 
-	public MessageFormatValue(final String format, final Collection values) {
-		this(new ConstantValue(format), values);
-	}
-	
-	/*
-	 * (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#getTags()
-	 */
-	public Set getTags() { return tags; }
+    public MessageFormatValue(final String format, final Iterable<Value> values) {
+        this(new ConstantValue(format), values);
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#getVariables()
-	 */
-	public Set getVariables() { return variables; }
+    /*
+     * (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#getTags()
+     */
+    public SortedSet<Integer> getTags() { return tags; }
 
-	/* (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#on(org.dcm4che2.data.DicomObject)
-	 */
-	public String on(final DicomObject o) throws ScriptEvaluationException {
-		final Object[] vals = new Object[values.size()];
-		logger.trace("format values: " + values);
-		for (int i = 0; i < values.size(); i++) {
-			vals[i] = ((Value)values.get(i)).on(o);
-		}
-		logger.trace("format args: " + Arrays.asList(vals));
-		return MessageFormat.format(format.on(o), vals);
-	}
+    /*
+     * (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#getVariables()
+     */
+    public Set<Variable> getVariables() { return variables; }
 
-	/*
-	 * (non-Javadoc)
-	 * @see org.nrg.dcm.edit.Value#on(java.util.Map)
-	 */
-	public String on(final 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);
-		}
-		return MessageFormat.format(format.on(m), vals);
-	}
+    /* (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#on(org.dcm4che2.data.DicomObject)
+     */
+    public String on(final DicomObject o) throws ScriptEvaluationException {
+        final Object[] vals = new Object[values.size()];
+        logger.trace("format values: {}", values);
+        for (int i = 0; i < values.size(); i++) {
+            vals[i] = ((Value)values.get(i)).on(o);
+        }
+        logger.trace("format args: {}", vals);
+        return MessageFormat.format(format.on(o), vals);
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#equals(java.lang.Object)
-	 */
-	public boolean equals(final Object o) {
-		if (o instanceof MessageFormatValue) {
-			final MessageFormatValue other = (MessageFormatValue)o;
-			return format.equals(other.format) && values.equals(other.values);
-		} else {
-			return false;
-		}
-	}
+    /*
+     * (non-Javadoc)
+     * @see org.nrg.dcm.edit.Value#on(java.util.Map)
+     */
+    public String on(final Map<Integer,String> 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);
+        }
+        return MessageFormat.format(format.on(m), vals);
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#hashCode()
-	 */
-	public int hashCode() {
-		int result = 17;
-		result = 37*result + format.hashCode();
-		result = 37*result + values.hashCode();
-		return result;
-	}
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(final Object o) {
+        if (o instanceof MessageFormatValue) {
+            final MessageFormatValue other = (MessageFormatValue)o;
+            return format.equals(other.format) && values.equals(other.values);
+        } else {
+            return false;
+        }
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * @see java.lang.Object#toString()
-	 */
-	public String toString() {
-		final StringBuffer sb = new StringBuffer("format(\"");
-		sb.append(format).append("\"");
-		for (final Iterator i = values.iterator(); i.hasNext(); ) {
-			sb.append(", ").append(i.next());
-		}
-		sb.append(")");
-		return sb.toString();
-	}
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return Objects.hashCode(format, values);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("format(\"");
+        sb.append(format).append("\"");
+        for (final Value v : values) {
+            sb.append(", ").append(v);
+        }
+        sb.append(")");
+        return sb.toString();
+    }
 }

File src/main/java/org/nrg/dcm/edit/NoOp.java

 /*
- * Copyright (c) 2006-2009 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 package org.nrg.dcm.edit;
 
 import java.util.Map;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
-import org.nrg.dcm.edit.AbstractAction;
+import com.google.common.collect.ImmutableSortedSet;
 
 /**
  * Null operation
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  */
 public class NoOp extends AbstractOperation {
-    private final static String id = "NoOp";
-    
-    public NoOp(final int tag) { super(id, tag); }
+    private static final SortedSet<Integer> EMPTY = ImmutableSortedSet.of();
+    private static final String id = "NoOp";
 
-    public NoOp() { this(0); }
-    
+    public NoOp() { super(id); }
+
     public Action makeAction(DicomObject o) throws AttributeException {
-	return new AbstractAction(getTag()) {
-	    public void apply() {}
-	    public String toString() { return id; }
-	};
+        return new Action() {
+            public void apply() {}
+            public String toString() { return id; }
+            public SortedSet<Integer> getTags() { return EMPTY; }
+        };
     }
 
-    public String apply(Map vals) {
-	return (String)vals.get(new Integer(getTag()));
+    public String apply(Map<Integer,String> vals) {
+        return null;
     }
 
     /*
      */
     public boolean equals(Object o) { return o instanceof NoOp; }
 
+    public SortedSet<Integer> getAffectedTags() { return EMPTY; }
+    public SortedSet<Integer> getRequiredTags() { return EMPTY; }
+
     /*
      * (non-Javadoc)
      * @see java.lang.Object#hashCode()

File src/main/java/org/nrg/dcm/edit/Operation.java

 /*
- * Copyright (c) 2006-2009 Washington University
+ * Copyright (c) 2006-2011 Washington University
  */
 
 package org.nrg.dcm.edit;
 
 import java.util.Map;
+import java.util.SortedSet;
 
 import org.dcm4che2.data.DicomObject;
 
 /**
- * @author Kevin A. Archie <karchie@npg.wustl.edu>
+ * @author Kevin A. Archie <karchie@wustl.edu>
  *
  */
 public interface Operation {
+    /**
+     * Creates an Action that is a concrete implementation of applying this
+     * Operation to the given DicomObject.
+     * @param o DicomObject to act on
+     * @return
+     * @throws AttributeException
+     */
     Action makeAction(DicomObject o) throws AttributeException;
-    String apply(Map vals) throws ScriptEvaluationException;
-    int getTag();
+
+    /**
+     * Compute the value that results from applying this Operation to
+     * the given object context.
+     * @param vals Map:Tag(Integer) -> Value(String) representing the object of the operation
+     * @return String value
+     * @throws ScriptEvaluationException
+     */