external/emma 2.0.5312
diff --git a/BUILD.txt b/BUILD.txt
new file mode 100644
index 0000000..60e7a58
--- /dev/null
+++ b/BUILD.txt
@@ -0,0 +1,43 @@
+
+REQUIREMENTS
+~~~~~~~~~~~~
+
+- JDK1.4 (the build will not work in JDK1.5)
+- Apache ANT 1.5+ (official build uses v1.6.1)
+
+BUILDING EMMA
+~~~~~~~~~~~~~
+
+- cd into the directory containing build.xml and create a file
+  "build.properties" that tells the build where your JDK and ANT are located:
+
+    - if Java 1.4 is not your currently installed Java version, set the
+      following property to point to the "jre" subdirectory of your JDK 1.4
+      install:
+
+build.target.j2se.14.home = <JDK 1.4 install>/jre
+
+    - if you don't have ANT_HOME environment variable set up to point to ANT
+    1.5 home directory, set the following property:
+
+build.target.ant.15.home = <ANT 1.5 install dir> 
+
+- From the directory containing build.xml, execute:
+
+>ant
+
+- After a successful build, EMMA jars will be located in
+  ${basedir}/${dist.dir}, which defaults to ./dist
+
+
+- To remove all generated build artifacts and reset the build to a clean
+  state, execute:
+
+>ant clean
+
+ __________________________________________________________________________
+       Copyright (C) 2003-2004 Vlad Roubtsov. All rights reserved.
+
+ This program and the accompanying materials are made available under
+ the terms of the Common Public License v1.0 which accompanies this
+ distribution, and is available at http://www.eclipse.org/legal/cpl-v10.html
diff --git a/TRACKING.txt b/TRACKING.txt
new file mode 100644
index 0000000..3ea627a
--- /dev/null
+++ b/TRACKING.txt
@@ -0,0 +1,5 @@
+
+fixed BUGs:
+
+1189166
+988160
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ANTMain.java b/ant/ant14/com/vladium/emma/ANTMain.java
new file mode 100644
index 0000000..bc36184
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ANTMain.java
@@ -0,0 +1,39 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ANTMain.java,v 1.1.1.1 2004/05/09 16:57:26 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import com.vladium.emma.IAppConstants;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+final class ANTMain
+{
+    // public: ................................................................
+    
+    
+    public static void main (final String [] args)
+    {
+        System.out.println ("this jar contains ANT task definitions for " + IAppConstants.APP_NAME
+            + " and is not meant to be executable");
+            
+        System.out.println ();
+        System.out.println (IAppConstants.APP_USAGE_BUILD_ID);
+    } 
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/FileTask.java b/ant/ant14/com/vladium/emma/ant/FileTask.java
new file mode 100644
index 0000000..6357799
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/FileTask.java
@@ -0,0 +1,136 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: FileTask.java,v 1.2.2.1 2004/07/08 10:52:10 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.types.FileSet;
+
+import com.vladium.emma.ant.XFileSet;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class FileTask extends NestedTask
+{
+    // public: ................................................................
+    
+    
+//    public static final class FileElement
+//    {
+//        public FileElement (final List /* File */ files)
+//        {
+//            m_files = files;
+//        }
+//        
+//        public void setFile (final File file)
+//        {
+//            if (file != null) m_files.add (file);
+//        }
+//        
+//        
+//        private final List /* File */ m_files;
+//        
+//    } // end of nested class
+    
+    
+    // infileset|fileset element:
+
+    public final void addInfileset (final XFileSet set)
+    {
+        if (set != null) m_dataFileSets.add (set);
+    }
+    
+    public final void addFileset (final XFileSet set)
+    {
+        if (set != null) m_dataFileSets.add (set);
+    }
+
+
+//    // infile|file element:
+//    
+//    public final FileElement createInfile ()
+//    {
+//        return new FileElement (m_dataFiles);
+//    }
+//    
+//    public final FileElement createFile ()
+//    {
+//        return new FileElement (m_dataFiles);
+//    }
+    
+    // protected: .............................................................
+    
+    
+    protected FileTask (final SuppressableTask parent)
+    {
+        super (parent);
+        
+        m_dataFileSets = new ArrayList ();
+//        m_dataFiles = new ArrayList ();
+    }
+
+
+    protected final String [] getDataPath (final boolean removeNonexistent)
+    {
+        final List /* String */ _files = new ArrayList ();
+            
+        // merge filesets:
+        for (Iterator i = m_dataFileSets.iterator (); i.hasNext (); )
+        {
+            final FileSet set = (FileSet) i.next ();
+            final DirectoryScanner ds = set.getDirectoryScanner (project);
+            final File dsBaseDir = ds.getBasedir ();
+            
+            final String [] dsfiles = ds.getIncludedFiles ();
+            for (int f = 0; f < dsfiles.length; ++ f)
+            {
+                _files.add (new File (dsBaseDir, dsfiles [f]).getAbsolutePath ());
+            }
+        }
+        
+//        // merge files:
+//        for (Iterator i = m_dataFiles.iterator (); i.hasNext (); )
+//        {
+//            final File file = (File) i.next ();
+//            if (! removeNonexistent || file.exists ())
+//            {
+//                _files.add (file.getAbsolutePath ());
+//            }
+//        }
+        
+        if (_files.size () == 0)
+            return EMPTY_STRING_ARRAY;
+        else
+        {            
+            final String [] files = new String [_files.size ()];
+            _files.toArray (files);
+            
+            return files;
+        }
+    }
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+   
+    
+    private final List /* FileSet */ m_dataFileSets; // never null
+//    private final List /* File */ m_dataFiles; // never null
+    
+    private static final String [] EMPTY_STRING_ARRAY = new String [0];
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/FilterTask.java b/ant/ant14/com/vladium/emma/ant/FilterTask.java
new file mode 100644
index 0000000..4af71e9
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/FilterTask.java
@@ -0,0 +1,76 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: FilterTask.java,v 1.1.1.1.2.1 2004/07/08 10:52:10 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.io.File;
+
+import com.vladium.util.Strings;
+import com.vladium.emma.instr.FilterCfg;
+import com.vladium.emma.instr.FilterCfg.filterElement;
+
+import org.apache.tools.ant.BuildException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class FilterTask extends NestedTask
+{
+    // public: ................................................................
+
+
+    public void init () throws BuildException
+    {
+        super.init ();
+        
+        m_filterCfg = new FilterCfg (this);
+    }
+
+    
+    // filter attribute/element:
+    
+    public final void setFilter (final String filter)
+    {
+        m_filterCfg.setFilter (filter);
+    }
+    
+    public final filterElement createFilter ()
+    {
+        return m_filterCfg.createFilter ();
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected FilterTask (final SuppressableTask parent)
+    {
+        super (parent);
+    }
+    
+    
+    protected final String [] getFilterSpecs ()
+    {
+        return m_filterCfg.getFilterSpecs ();
+    }
+        
+
+    protected static final String COMMA               = ",";
+    protected static final String COMMA_DELIMITERS    = COMMA + Strings.WHITE_SPACE;
+    protected static final String PATH_DELIMITERS     = COMMA.concat (File.pathSeparator);
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+
+    private /*final*/ FilterCfg m_filterCfg;    
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/GenericCfg.java b/ant/ant14/com/vladium/emma/ant/GenericCfg.java
new file mode 100644
index 0000000..02ebae6
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/GenericCfg.java
@@ -0,0 +1,150 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: GenericCfg.java,v 1.1.1.1.2.1 2004/07/08 10:52:10 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import com.vladium.emma.EMMAProperties;
+import com.vladium.util.IProperties;
+import com.vladium.util.Property;
+
+// ----------------------------------------------------------------------------
+/**
+ * GenericCfg is a simple container for 'generic' properties, i.e., properties
+ * that are set via generic 'properties=&lt;file&gt;' attribute and &lt;property&gt;
+ * nested elements. This class makes no decision about relative priorities for
+ * propertie set in an external file or via nested elements, leaving this up
+ * to the parent.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class GenericCfg
+{
+    // public: ................................................................
+    
+
+    public GenericCfg (final Task task)
+    {
+        if (task == null) throw new IllegalArgumentException ("null input: task");
+        
+        m_task = task;
+        m_genericPropertyElements = new ArrayList ();
+    }
+    
+
+    // .properties file attribute [actual file I/O done lazily by getFileSettings()]:
+    
+    public void setProperties (final File file)
+    {
+        m_settingsFile = file; // actual file I/O is done in getFileSettings()
+    }
+
+    // generic property element:
+    
+    public PropertyElement createProperty ()
+    {
+        m_genericSettings = null;
+        
+        final PropertyElement property = new PropertyElement ();
+        m_genericPropertyElements.add (property);
+        
+        return property;
+    }
+
+    // ACCESSORS:
+
+    public IProperties getFileSettings ()
+    {
+        IProperties fileSettings = m_fileSettings;
+        if ((fileSettings == null) && (m_settingsFile != null))
+        {
+            try
+            {
+                fileSettings = EMMAProperties.wrap (Property.getPropertiesFromFile (m_settingsFile));
+            }
+            catch (IOException ioe)
+            {
+                throw (BuildException) SuppressableTask.newBuildException (m_task.getTaskName ()
+                    + ": property file [" + m_settingsFile.getAbsolutePath () + "] could not be read" , ioe, m_task.getLocation ()).fillInStackTrace ();
+            }
+            
+            m_fileSettings = fileSettings;
+            
+            return fileSettings;
+        }
+        
+        return fileSettings;
+    }
+    
+    public IProperties getGenericSettings ()
+    {
+        IProperties genericSettings = m_genericSettings;
+        if (genericSettings == null)
+        {
+            genericSettings = EMMAProperties.wrap (new Properties ());
+            
+            for (Iterator i = m_genericPropertyElements.iterator (); i.hasNext (); )
+            {
+                final PropertyElement property = (PropertyElement) i.next ();
+                
+                final String name = property.getName ();
+                String value = property.getValue ();
+                if (value == null) value = "";
+                
+                if (name != null)
+                {
+                    // [assertion: name != null, value != null]
+                    
+                    final String currentValue = genericSettings.getProperty (name);  
+                    if ((currentValue != null) && ! value.equals (currentValue))
+                    {
+                        throw (BuildException) SuppressableTask.newBuildException (m_task.getTaskName ()
+                            + ": conflicting settings for property [" + name + "]: [" + value + "]" , m_task.getLocation ()).fillInStackTrace ();
+                    }
+                    else
+                    {
+                        genericSettings.setProperty (name, value);
+                    }
+                }
+            }
+            
+            m_genericSettings = genericSettings;
+            
+            return genericSettings;
+        }
+        
+        return genericSettings;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final Task m_task;
+        
+    private final List /* PropertyElement */ m_genericPropertyElements; // never null
+    private File m_settingsFile; // can be null
+    
+    private transient IProperties m_fileSettings; // can be null
+    private transient IProperties m_genericSettings; // can be null
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/NestedTask.java b/ant/ant14/com/vladium/emma/ant/NestedTask.java
new file mode 100644
index 0000000..cd2c0ad
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/NestedTask.java
@@ -0,0 +1,66 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: NestedTask.java,v 1.1.1.1.2.1 2004/07/08 10:52:10 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import com.vladium.util.IProperties;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class NestedTask extends SuppressableTask
+{
+    // public: ................................................................
+    
+    // protected: .............................................................
+    
+    
+    protected NestedTask (final SuppressableTask parent)
+    {
+        if (parent == null)
+            throw new IllegalArgumentException ("null input: parent");
+        
+        m_parent = parent;
+    }
+
+    /**
+     * Overrides {@link SuppressableTask#getTaskSettings()} to mix in parent
+     * task settings as the base settings. 
+     */
+    protected final IProperties getTaskSettings ()
+    {
+        final IProperties parentSettings = m_parent != null
+            ? m_parent.getTaskSettings ()
+            : null;
+        
+        final IProperties taskOverrides = super.getTaskSettings ();
+        
+        // task settings are always more specific than parent settings, but attention
+        // needs to be paid to horizontal inheritance:
+        
+        if (parentSettings == null)
+            return taskOverrides;
+        else
+        {
+            final IProperties settings = IProperties.Factory.combine (taskOverrides, parentSettings);
+        
+            return settings;
+        }
+    }
+
+    
+    protected final SuppressableTask m_parent;
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/PropertyElement.java b/ant/ant14/com/vladium/emma/ant/PropertyElement.java
new file mode 100644
index 0000000..2d002bc
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/PropertyElement.java
@@ -0,0 +1,57 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: PropertyElement.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class PropertyElement
+{
+    // public: ................................................................
+    
+    
+    public PropertyElement ()
+    {
+        // ensure the constructor is always public
+    }
+    
+
+    public String getName ()
+    {
+        return m_name;
+    }
+    
+    public String getValue ()
+    {
+        return m_value;
+    }
+
+    public void setName (final String name)
+    {
+        m_name = name;
+    }
+    
+    public void setValue (final String value)
+    {
+        m_value = value;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+
+    private String m_name, m_value;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/StringValue.java b/ant/ant14/com/vladium/emma/ant/StringValue.java
new file mode 100644
index 0000000..454c2ce
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/StringValue.java
@@ -0,0 +1,65 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: StringValue.java,v 1.2 2004/05/20 02:28:06 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import org.apache.tools.ant.Task;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class StringValue
+{
+    // public: ................................................................
+    
+    
+    public void appendValue (final String value, final String separator)
+    {
+        if ((value != null) && (value.length () > 0))
+        {
+            if (m_value == null)
+            {
+                m_value = new StringBuffer (value); 
+            }
+            else
+            {
+                m_value.append (separator);
+                m_value.append (value); // no trailing separator kept
+            }
+        }
+    }
+                
+    public String getValue ()
+    {
+        return m_value != null ? m_value.toString () : null;  
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected StringValue (final Task task)
+    {
+        if (task == null) throw new IllegalArgumentException ("null input: task");
+        
+        m_task = task;
+    }
+
+
+    protected final Task m_task;
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+    
+    private StringBuffer m_value;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/SuppressableTask.java b/ant/ant14/com/vladium/emma/ant/SuppressableTask.java
new file mode 100644
index 0000000..ff90e58
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/SuppressableTask.java
@@ -0,0 +1,132 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SuppressableTask.java,v 1.1.1.1.2.2 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.io.File;
+
+import com.vladium.emma.IAppConstants;
+import com.vladium.util.IProperties;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Location;
+import org.apache.tools.ant.Task;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class SuppressableTask extends Task
+{
+    // public: ................................................................
+    
+    
+    public void init () throws BuildException
+    {
+        super.init ();
+        
+        m_verbosityCfg = new VerbosityCfg ();
+        m_genericCfg = new GenericCfg (this);
+    }
+
+    /**
+     * Set the optional 'enabled' attribute [defaults to 'true'].
+     */
+    public final void setEnabled (final boolean enabled)
+    {
+        m_enabled = enabled;
+    }
+    
+    public final boolean isEnabled ()
+    {
+        return m_enabled;
+    }
+    
+    // verbosity attribute:
+    
+    public void setVerbosity (final VerbosityCfg.VerbosityAttribute verbosity)
+    {
+        m_verbosityCfg.setVerbosity (verbosity);
+    }
+    
+    // verbosity class filter attribute:
+    
+    public void setVerbosityfilter (final String filter)
+    {
+        m_verbosityCfg.setVerbosityfilter (filter);
+    }
+
+    // .properties file attribute:
+    
+    public final void setProperties (final File file)
+    {
+        m_genericCfg.setProperties (file);
+    }
+
+    // generic property element:
+    
+    public final PropertyElement createProperty ()
+    {
+        return m_genericCfg.createProperty ();
+    }
+    
+    
+    public static BuildException newBuildException (final String msg, final Location location)
+    {
+        final String prefixedMsg = ((msg == null) || (msg.length () == 0))
+            ? msg
+            : IAppConstants.APP_THROWABLE_BUILD_ID + " " + msg;
+       
+        return new BuildException (prefixedMsg, location); 
+    }
+    
+    public static BuildException newBuildException (final String msg, final Throwable cause, final Location location)
+    {
+        final String prefixedMsg = ((msg == null) || (msg.length () == 0))
+            ? msg
+            : IAppConstants.APP_THROWABLE_BUILD_ID + " " + msg;
+       
+        return new BuildException (prefixedMsg, cause, location); 
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected SuppressableTask ()
+    {
+        m_enabled = true; // by default, all tasks are enabled
+    }
+    
+    protected IProperties getTaskSettings ()
+    {
+        // (1) by default, generic settings are always more specific than any file settings
+        
+        // (2) verbosity settings use dedicated attributes and hence are more specific
+        // than anything generic
+        
+        final IProperties fileSettings = m_genericCfg.getFileSettings ();
+        final IProperties genericSettings = m_genericCfg.getGenericSettings (); 
+        final IProperties verbositySettings = m_verbosityCfg.getSettings ();
+        
+        return IProperties.Factory.combine (verbositySettings,
+               IProperties.Factory.combine (genericSettings,
+                                            fileSettings));
+    }    
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+    
+    private /*final*/ VerbosityCfg m_verbosityCfg;
+    private /*final*/ GenericCfg m_genericCfg;
+    private boolean m_enabled;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/ant/VerbosityCfg.java b/ant/ant14/com/vladium/emma/ant/VerbosityCfg.java
new file mode 100644
index 0000000..41d9842
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/ant/VerbosityCfg.java
@@ -0,0 +1,102 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: VerbosityCfg.java,v 1.1.2.1 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.util.Properties;
+
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+import com.vladium.emma.AppLoggers;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.logging.ILogLevels;
+import com.vladium.util.IProperties;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+final class VerbosityCfg
+{
+    // public: ................................................................
+    
+
+    public static final class VerbosityAttribute extends EnumeratedAttribute
+    {
+        public String [] getValues ()
+        {
+            return VALUES;
+        }
+        
+        private static final String [] VALUES = new String []
+            {
+                ILogLevels.SEVERE_STRING,
+                ILogLevels.SILENT_STRING,
+                ILogLevels.WARNING_STRING,
+                ILogLevels.QUIET_STRING,
+                ILogLevels.INFO_STRING,
+                ILogLevels.VERBOSE_STRING,
+                ILogLevels.TRACE1_STRING,
+                ILogLevels.TRACE2_STRING,
+                ILogLevels.TRACE3_STRING,
+            };
+
+    } // end of nested class
+    
+
+    // verbosity attribute:
+    
+    public void setVerbosity (final VerbosityAttribute verbosity)
+    {
+        m_verbosity = verbosity.getValue ();
+    }
+    
+    // verbosity class filter attribute:
+    
+    public void setVerbosityfilter (final String filter)
+    {
+        m_verbosityFilter = filter;
+    }
+    
+    // ACCESSORS:
+    
+    public IProperties getSettings ()
+    {
+        IProperties settings = m_settings;
+        if (settings == null)
+        {
+            settings = EMMAProperties.wrap (new Properties ());
+            
+            if ((m_verbosity != null) && (m_verbosity.trim ().length () > 0))
+                settings.setProperty (AppLoggers.PROPERTY_VERBOSITY_LEVEL, m_verbosity.trim ());
+            
+            if ((m_verbosityFilter != null) && (m_verbosityFilter.trim ().length () > 0))
+                settings.setProperty (AppLoggers.PROPERTY_VERBOSITY_FILTER, m_verbosityFilter.trim ());
+            
+            m_settings = settings;
+            return settings;
+        }
+        
+        return settings;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private String m_verbosity;
+    private String m_verbosityFilter;
+    
+    private transient IProperties m_settings; // can be null
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/data/mergeTask.java b/ant/ant14/com/vladium/emma/data/mergeTask.java
new file mode 100644
index 0000000..361f3e9
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/data/mergeTask.java
@@ -0,0 +1,102 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: mergeTask.java,v 1.1.1.1.2.1 2004/07/08 10:52:09 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.File;
+
+import org.apache.tools.ant.BuildException;
+
+import com.vladium.emma.ant.FileTask;
+import com.vladium.emma.ant.SuppressableTask;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class mergeTask extends FileTask 
+{
+    // public: ................................................................
+    
+    
+    public mergeTask (final SuppressableTask parent)
+    {
+        super (parent);
+    }
+    
+    public void execute () throws BuildException
+    {
+        if (isEnabled ())
+        {
+            String [] files = getDataPath (true);
+            if ((files == null) || (files.length == 0))
+                throw (BuildException) newBuildException (getTaskName ()
+                    + ": no valid input data files have been specified", location).fillInStackTrace ();
+            
+            final MergeProcessor processor = MergeProcessor.create ();
+            
+            processor.setDataPath (files); files = null;
+            processor.setSessionOutFile (m_outFile != null ? m_outFile.getAbsolutePath () : null);
+            processor.setPropertyOverrides (getTaskSettings ());
+            
+            processor.run ();
+        }
+    }
+    
+
+    // mergefile|tofile|outfile|file attribute:
+    
+    public void setMergefile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": merge data file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+    
+    public void setOutfile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": merge data file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+
+    public void setTofile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": merge data file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+    
+    public void setFile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": merge data file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+    
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private File m_outFile;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/emmaTask.java b/ant/ant14/com/vladium/emma/emmaTask.java
new file mode 100644
index 0000000..6aa726f
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/emmaTask.java
@@ -0,0 +1,117 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: emmaTask.java,v 1.1.1.1.2.2 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import com.vladium.emma.IAppConstants;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+import com.vladium.emma.ant.NestedTask;
+import com.vladium.emma.ant.SuppressableTask;
+import com.vladium.emma.data.mergeTask;
+import com.vladium.emma.instr.instrTask;
+import com.vladium.emma.report.reportTask;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class emmaTask extends SuppressableTask
+{
+    // public: ................................................................
+    
+    // TODO: this and related tasks should be designed for external extensibility
+    // [make non final and use virtual prop getters]
+    
+    public emmaTask ()
+    {
+        m_tasks = new ArrayList ();
+    }
+    
+    
+    public synchronized void execute () throws BuildException
+    {
+        log (IAppConstants.APP_VERBOSE_BUILD_ID, Project.MSG_VERBOSE);
+        
+        if (isEnabled ())
+        {
+            while (! m_tasks.isEmpty ())
+            {
+                final NestedTask task = (NestedTask) m_tasks.remove (0);
+                
+                final String name = getTaskName ();
+                try
+                {
+                    setTaskName (task.getTaskName ());
+                    
+                    task.execute ();
+                }
+                finally
+                {
+                    setTaskName (name);
+                }
+            }
+        }
+    }
+
+    
+    public NestedTask createInstr ()
+    {
+        return addTask (new instrTask (this), getNestedTaskName ("instr"));
+    }
+    
+    public NestedTask createMerge ()
+    {
+        return addTask (new mergeTask (this), getNestedTaskName ("merge"));
+    }
+    
+    public NestedTask createReport ()
+    {
+        return addTask (new reportTask (this), getNestedTaskName ("report"));
+    }
+    
+    // protected: .............................................................
+
+
+    protected NestedTask addTask (final NestedTask task, final String pseudoName)
+    {
+        initTask (task, pseudoName);
+        
+        m_tasks.add (task);
+        return task;
+    }
+    
+    protected void initTask (final NestedTask task, final String pseudoName)
+    {
+        task.setTaskName (pseudoName);
+        task.setProject (getProject ());
+        task.setLocation (getLocation ());
+        task.setOwningTarget (getOwningTarget ());
+        
+        task.init ();
+    }
+    
+    protected String getNestedTaskName (final String subname)
+    {
+        return getTaskName ().concat (".").concat (subname);
+    }
+        
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final List /* NestedTask */ m_tasks;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/emmajavaTask.java b/ant/ant14/com/vladium/emma/emmajavaTask.java
new file mode 100644
index 0000000..cf4339a
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/emmajavaTask.java
@@ -0,0 +1,587 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: emmajavaTask.java,v 1.1.1.1.2.2 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import java.io.File;
+
+import com.vladium.util.IProperties;
+import com.vladium.util.Strings;
+import com.vladium.emma.ant.*;
+import com.vladium.emma.instr.FilterCfg;
+import com.vladium.emma.instr.FilterCfg.filterElement;
+import com.vladium.emma.report.ReportCfg;
+import com.vladium.emma.report.IReportEnums.DepthAttribute;
+import com.vladium.emma.report.IReportEnums.UnitsTypeAttribute;
+import com.vladium.emma.report.ReportCfg.Element_HTML;
+import com.vladium.emma.report.ReportCfg.Element_TXT;
+import com.vladium.emma.report.ReportCfg.Element_XML;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Java;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class emmajavaTask extends Java
+{
+    // public: ................................................................
+    
+    
+    public void init () throws BuildException
+    {
+        super.init ();
+
+        m_verbosityCfg = new VerbosityCfg ();
+        m_genericCfg = new GenericCfg (this);
+        m_filterCfg = new FilterCfg (this);
+        m_reportCfg = new ReportCfg (project, this);
+        setEnabled (true);        
+    }
+
+    
+    public void execute () throws BuildException
+    {
+        log (IAppConstants.APP_VERBOSE_BUILD_ID, Project.MSG_VERBOSE);
+        
+        if (getClasspath () == null)
+            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                + ": this task requires 'classpath' attribute to be set", location).fillInStackTrace ();
+            
+
+        if (isEnabled ())
+        {
+            // fork:
+            if (m_forkUserOverride && ! m_fork)
+                log (getTaskName () + ": 'fork=\"false\"' attribute setting ignored (this task always forks)", Project.MSG_WARN);
+            
+            super.setFork (true); // always fork
+            
+            // add emma libs to the parent task's classpath [to support non-extdir deployment]:
+            final Path libClasspath = m_libClasspath;
+            if ((libClasspath != null) && (libClasspath.size () > 0))
+            {
+                super.createClasspath ().append (libClasspath);
+            }
+            
+            // classname|jar (1/2):
+            super.setClassname ("emmarun");
+            
+            // <emmajava> extensions:
+            {
+                // report types:
+                {
+                    String reportTypes = Strings.toListForm (m_reportCfg.getReportTypes (), ',');
+                    if ((reportTypes == null) || (reportTypes.length () == 0)) reportTypes = "txt";
+                    
+                    super.createArg ().setValue ("-r");
+                    super.createArg ().setValue (reportTypes);
+                }
+                
+                // full classpath scan flag:
+                {
+                    if (m_scanCoveragePath)
+                    {
+                        super.createArg ().setValue ("-f");
+                    }
+                }
+                
+                // dump raw data flag and options:
+                {
+                    if (m_dumpSessionData)
+                    {
+                        super.createArg ().setValue ("-raw");
+                        
+                        if (m_outFile != null)
+                        {
+                            super.createArg ().setValue ("-out");
+                            super.createArg ().setValue (m_outFile.getAbsolutePath ());
+                        }
+                        
+                        if (m_outFileMerge != null)
+                        {
+                            super.createArg ().setValue ("-merge");
+                            super.createArg ().setValue (m_outFileMerge.booleanValue () ? "y" : "n");
+                        }
+                    }
+                    else
+                    {
+                        if (m_outFile != null)
+                            log (getTaskName () + ": output file attribute ignored ('fullmetadata=\"true\"' not specified)", Project.MSG_WARN);
+                        
+                        if (m_outFileMerge != null)
+                            log (getTaskName () + ": merge attribute setting ignored ('fullmetadata=\"true\"' not specified)", Project.MSG_WARN);
+                    }
+                } 
+                
+                // instr filter:
+                {
+                    final String [] specs = m_filterCfg.getFilterSpecs ();
+                    if ((specs != null) && (specs.length > 0))
+                    {
+                        super.createArg ().setValue ("-ix");
+                        super.createArg ().setValue (Strings.toListForm (specs, ','));
+                    }
+                }
+                
+                // sourcepath:
+                {
+                    final Path srcpath = m_reportCfg.getSourcepath ();
+                    if (srcpath != null)
+                    {
+                        super.createArg ().setValue ("-sp");
+                        super.createArg ().setValue (Strings.toListForm (srcpath.list (), ','));
+                    }
+                }
+                
+                // all other generic settings:
+                {
+                    final IProperties reportSettings = m_reportCfg.getReportSettings ();
+                    final IProperties genericSettings = m_genericCfg.getGenericSettings ();
+                    
+                    // TODO: another options is to read this file in the forked JVM [use '-props' pass-through]
+                    // the best option depends on how ANT resolves relative file names 
+                    final IProperties fileSettings = m_genericCfg.getFileSettings ();
+                    
+                    // verbosity settings use dedicated attributes and hence are more specific
+                    // than anything generic:
+                    final IProperties verbositySettings = m_verbosityCfg.getSettings ();
+                    
+                    // (1) file settings have lower priority than any explicitly named overrides
+                    // (2) named report settings override generic named settings
+                    // (3) verbosity settings use dedicated attributes (not overlapping with report
+                    // cfg) and hence are more specific than anything generic
+                    final IProperties settings = IProperties.Factory.combine (reportSettings,
+                                                 IProperties.Factory.combine (verbositySettings,
+                                                 IProperties.Factory.combine (genericSettings,
+                                                                              fileSettings)));
+                    
+                    final String [] argForm = settings.toAppArgsForm ("-D");
+                    if (argForm.length > 0)
+                    {
+                        for (int a = 0; a < argForm.length; ++ a)
+                            super.createArg ().setValue (argForm [a]);
+                    }
+                }
+            }
+            
+            // [assertion: getClasspath() is not null]
+            
+            // classpath:
+            super.createArg ().setValue ("-cp");
+            super.createArg ().setPath (getClasspath ());
+                        
+            // classname|jar (2/2):
+            if (getClassname () != null)
+                super.createArg ().setValue (getClassname ());
+            else if (getJar () != null)
+            {
+                super.createArg ().setValue ("-jar");
+                super.createArg ().setValue (getJar ().getAbsolutePath ());
+            }
+            else
+                throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                    + "either 'jar' or 'classname' attribute must be set", location).fillInStackTrace ();
+                
+            // main class args:
+            if (m_appArgs != null)
+            {
+                final String [] args = m_appArgs.getArguments ();
+                for (int a = 0; a < args.length; ++ a)
+                {
+                    super.createArg ().setValue (args [a]); // note: spaces etc are escaped correctly by ANT libs
+                }
+            }
+        }
+        else
+        {
+            // fork:
+            super.setFork (m_fork);
+            
+            // [assertion: getClasspath() is not null]
+            
+            // classpath:
+            super.createClasspath ().append (getClasspath ()); // can't use setClasspath() for obvious reasons
+            
+            // classname|jar:
+            if (getClassname () != null)
+                super.setClassname (getClassname ());
+            else if (getJar () != null)
+                super.setJar (getJar ());
+            else
+                throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                    + "either 'jar' or 'classname' attribute must be set", location).fillInStackTrace ();
+            
+            // main class args:
+            if (m_appArgs != null)
+            {
+                final String [] args = m_appArgs.getArguments ();
+                for (int a = 0; a < args.length; ++ a)
+                {
+                    super.createArg ().setValue (args [a]); // note: spaces etc are escaped correctly by ANT libs
+                }
+            }    
+        }
+        
+        super.execute ();
+    }
+
+    
+    
+    // <java> overrides [ANT 1.4]:
+    
+    public void setClassname (final String classname)
+    {
+        if (getJar () != null)
+            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                + "'jar' and 'classname' attributes cannot be set at the same time", location).fillInStackTrace ();
+            
+        m_classname = classname;
+    }
+    
+    public void setJar (final File file)
+    {
+        if (getClassname () != null)
+            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                + "'jar' and 'classname' attributes cannot be set at the same time", location).fillInStackTrace ();
+            
+        m_jar = file;
+    }
+    
+    
+    public void setClasspath (final Path path)
+    {
+        if (m_classpath == null)
+            m_classpath = path;
+        else
+            m_classpath.append (path);
+    }
+    
+    public void setClasspathRef (final Reference ref)
+    {
+        createClasspath ().setRefid (ref);
+    }
+    
+    public Path createClasspath ()
+    {
+        if (m_classpath == null)
+            m_classpath = new Path (project);
+        
+        return m_classpath.createPath ();
+    }
+    
+    /**
+     * This is already deprecated in ANT v1.4. However, it is still supported by
+     * the parent task so I do likewise.
+     */ 
+    public void setArgs (final String args)
+    {
+        throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+            + ": disallows using <java>'s deprecated 'args' attribute", location).fillInStackTrace ();
+    }
+
+    /**
+     * Not overridable.
+     */
+    public final void setFork (final boolean fork)
+    {
+        m_fork = fork;
+        m_forkUserOverride = true;
+    }
+    
+    /**
+     * Not overridable [due to limitations in ANT's Commandline].
+     */
+    public final Commandline.Argument createArg ()
+    {
+        if (m_appArgs == null)
+            m_appArgs = new Commandline ();
+        
+        return m_appArgs.createArgument ();
+    }
+    
+    // <java> overrides [ANT 1.5]:
+    
+    // [nothing at this point]
+    
+    
+    // <emmajava> extensions:
+    
+    public void setEnabled (final boolean enabled)
+    {
+        m_enabled = enabled;
+    }
+    
+    // .properties file attribute:
+    
+    public final void setProperties (final File file)
+    {
+        m_genericCfg.setProperties (file);
+    }
+
+    // generic property element:
+    
+    public final PropertyElement createProperty ()
+    {
+        return m_genericCfg.createProperty ();
+    }
+    
+    // verbosity attribute:
+    
+    public void setVerbosity (final VerbosityCfg.VerbosityAttribute verbosity)
+    {
+        m_verbosityCfg.setVerbosity (verbosity);
+    }
+    
+    // verbosity class filter attribute:
+    
+    public void setVerbosityfilter (final String filter)
+    {
+        m_verbosityCfg.setVerbosityfilter (filter);
+    }
+    
+    // lib classpath attribute [to support non-extdir deployment]:
+    
+    public final void setLibclasspath (final Path classpath)
+    {
+        if (m_libClasspath == null)
+            m_libClasspath = classpath;
+        else
+            m_libClasspath.append (classpath);
+    }
+    
+    public final void setLibclasspathRef (final Reference ref)
+    {
+        if (m_libClasspath == null)
+            m_libClasspath = new Path (project);
+        
+        m_libClasspath.createPath ().setRefid (ref);
+    }
+    
+    // -f flag:
+    
+    public void setFullmetadata (final boolean full)
+    {
+        m_scanCoveragePath = full; // defaults to false TODO: maintain the default in a central location
+    }
+    
+    // -raw flag:
+    
+    public void setDumpsessiondata (final boolean dump)
+    {
+        m_dumpSessionData = dump;
+    }
+    
+    // -out option:
+    
+    // sessiondatafile|outfile attribute:
+    
+    public void setSessiondatafile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                + ": session data file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+    
+    public void setOutfile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+                + ": session data file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+
+//    public void setTofile (final File file)
+//    {
+//        if (m_outFile != null)
+//            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+//                + ": session data file attribute already set", location).fillInStackTrace ();
+//            
+//        m_outFile = file;
+//    }
+//    
+//    public void setFile (final File file)
+//    {
+//        if (m_outFile != null)
+//            throw (BuildException) SuppressableTask.newBuildException (getTaskName ()
+//                + ": session data file attribute already set", location).fillInStackTrace ();
+//            
+//        m_outFile = file;
+//    }
+    
+    
+    // merge attribute:
+     
+    public void setMerge (final boolean merge)
+    {
+        m_outFileMerge = merge ? Boolean.TRUE : Boolean.FALSE;       
+    }
+    
+    // instr filter attribute/element:
+    
+    public final void setFilter (final String filter)
+    {
+        m_filterCfg.setFilter (filter);
+    }
+    
+    public final filterElement createFilter ()
+    {
+        return m_filterCfg.createFilter ();
+    }
+
+    
+    // TODO: should what's below go inside <report></report> ?
+    
+    // sourcepath attribute/element:
+    
+    public final void setSourcepath (final Path path)
+    {
+        m_reportCfg.setSourcepath (path);
+    }
+    
+    public final void setSourcepathRef (final Reference ref)
+    {
+        m_reportCfg.setSourcepathRef (ref);
+    }
+    
+    public final Path createSourcepath ()
+    {
+        return m_reportCfg.createSourcepath ();
+    }
+    
+    
+    // generator elements:
+    
+    public final Element_TXT createTxt ()
+    {
+        return m_reportCfg.createTxt ();
+    }
+    
+    public final Element_HTML createHtml ()
+    {
+        return m_reportCfg.createHtml ();
+    }
+    
+    public final Element_XML createXml ()
+    {
+        return m_reportCfg.createXml ();
+    }
+    
+    
+    // report properties [defaults for all report types]:
+
+    public final void setUnits (final UnitsTypeAttribute units)
+    {
+        m_reportCfg.setUnits (units);
+    }    
+
+    public final void setDepth (final DepthAttribute depth)
+    {
+        m_reportCfg.setDepth (depth);
+    }
+    
+    public final void setColumns (final String columns)
+    {
+        m_reportCfg.setColumns (columns);
+    }
+    
+    public final void setSort (final String sort)
+    {
+        m_reportCfg.setSort (sort);
+    }
+    
+    public final void setMetrics (final String metrics)
+    {
+        m_reportCfg.setMetrics (metrics);
+    }
+    
+    // these are not supported anymore
+    
+//    public final void setOutdir (final File dir)
+//    {
+//        m_reportCfg.setOutdir (dir);
+//    }
+//    
+//    public final void setDestdir (final File dir)
+//    {
+//        m_reportCfg.setDestdir (dir);
+//    }
+
+      // should be set at this level [and conflicts with raw data opts]:
+          
+//    public void setOutfile (final String fileName)
+//    {
+//        m_reportCfg.setOutfile (fileName);
+//    }
+    
+    public void setEncoding (final String encoding)
+    {
+        m_reportCfg.setEncoding (encoding);
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected String getClassname ()
+    {
+        return m_classname;
+    }
+    
+    protected File getJar ()
+    {
+        return m_jar;
+    }
+    
+    protected Path getClasspath ()
+    {
+        return m_classpath;
+    }
+    
+    // extended functionality:
+    
+    protected boolean isEnabled ()
+    {
+        return m_enabled;
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    // <java> overrides:
+    
+    private Path m_classpath;
+    private String m_classname;
+    private File m_jar;
+    private Commandline m_appArgs;
+    private boolean m_fork, m_forkUserOverride;
+    
+    // <emmajava> extensions:
+    
+    private boolean m_enabled;
+    private Path m_libClasspath;
+    private /*final*/ VerbosityCfg m_verbosityCfg;
+    private /*final*/ GenericCfg m_genericCfg;
+    private /*final*/ FilterCfg m_filterCfg;
+    private /*final*/ ReportCfg m_reportCfg;
+    private boolean m_scanCoveragePath; // defaults to false 
+    private boolean m_dumpSessionData; //defaults to false
+    private File m_outFile;
+    private Boolean m_outFileMerge;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/instr/FilterCfg.java b/ant/ant14/com/vladium/emma/instr/FilterCfg.java
new file mode 100644
index 0000000..1d13cae
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/instr/FilterCfg.java
@@ -0,0 +1,195 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: FilterCfg.java,v 1.2 2004/05/20 02:28:07 vlad_r Exp $
+ */
+package com.vladium.emma.instr;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import com.vladium.util.IConstants;
+import com.vladium.util.Strings;
+import com.vladium.emma.ant.StringValue;
+import com.vladium.emma.ant.SuppressableTask;
+import com.vladium.emma.filter.IInclExclFilter;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class FilterCfg
+{
+    // public: ................................................................
+    
+    
+    public static final class filterElement extends StringValue
+    {
+        public filterElement (final Task task)
+        {
+            super (task);
+        }
+        
+        public void setValue (final String value)
+        {
+            final String [] specs = Strings.merge (new String [] {value}, COMMA_DELIMITERS, true);
+            
+            for (int i = 0; i < specs.length; ++ i)
+            {
+                final String spec = specs [i];
+                
+                if (spec.startsWith (IInclExclFilter.INCLUSION_PREFIX_STRING) ||
+                    spec.startsWith (IInclExclFilter.EXCLUSION_PREFIX_STRING))
+                {
+                    appendValue (spec, COMMA);
+                }
+                else
+                {
+                    appendValue (IInclExclFilter.INCLUSION_PREFIX + spec, COMMA); // default to inclusion
+                }
+            }
+        }
+        
+        /**
+         * Set the 'file' attribute.
+         */
+        public void setFile (final File file)
+        {
+            appendValue ("@".concat (file.getAbsolutePath ()), COMMA); // actual file I/O delayed until getFilterSpecs()
+        }
+        
+        public void setIncludes (final String value)
+        {
+            final String [] specs = Strings.merge (new String [] {value}, COMMA_DELIMITERS, true);
+            
+            for (int i = 0; i < specs.length; ++ i)
+            {
+                final String spec = specs [i];
+                
+                if (spec.startsWith (IInclExclFilter.INCLUSION_PREFIX_STRING))
+                {
+                    appendValue (spec, COMMA);
+                }
+                else
+                {
+                    if (spec.startsWith (IInclExclFilter.EXCLUSION_PREFIX_STRING))
+                        appendValue (IInclExclFilter.INCLUSION_PREFIX + spec.substring (1), COMMA); // override
+                    else
+                        appendValue (IInclExclFilter.INCLUSION_PREFIX + spec, COMMA);
+                }
+            }
+        }
+        
+        public void setExcludes (final String value)
+        {
+            final String [] specs = Strings.merge (new String [] {value}, COMMA_DELIMITERS, true);
+            
+            for (int i = 0; i < specs.length; ++ i)
+            {
+                final String spec = specs [i];
+                
+                if (spec.startsWith (IInclExclFilter.EXCLUSION_PREFIX_STRING))
+                {
+                    appendValue (spec, COMMA);
+                }
+                else
+                {
+                    if (spec.startsWith (IInclExclFilter.INCLUSION_PREFIX_STRING))
+                        appendValue (IInclExclFilter.EXCLUSION_PREFIX + spec.substring (1), COMMA); // override
+                    else
+                        appendValue (IInclExclFilter.EXCLUSION_PREFIX + spec, COMMA);
+                }
+            }
+        }
+        
+    } // end of nested class
+    
+
+    public FilterCfg (final Task task)
+    {
+        if (task == null) throw new IllegalArgumentException ("null input: task");
+        
+        m_task = task;
+        m_filterList = new ArrayList ();
+    }
+
+    
+    // filter attribute/element:
+    
+    public void setFilter (final String filter)
+    {
+        createFilter ().appendValue (filter, COMMA);
+    }
+    
+    public filterElement createFilter ()
+    {
+        final filterElement result = new filterElement (m_task);
+        m_filterList.add (result);
+        
+        return result;
+    }
+    
+    // ACCESSORS:
+
+    public String [] getFilterSpecs ()
+    {
+        if (m_specs != null)
+            return m_specs;
+        else
+        {           
+            if ((m_filterList == null) || m_filterList.isEmpty ())
+            {
+                m_specs = IConstants.EMPTY_STRING_ARRAY;
+            }
+            else
+            {
+                final String [] values = new String [m_filterList.size ()];
+                
+                int j = 0;
+                for (Iterator i = m_filterList.iterator (); i.hasNext (); ++ j)
+                    values [j] = ((StringValue) i.next ()).getValue ();
+                                
+                try
+                {
+                    m_specs = Strings.mergeAT (values, COMMA_DELIMITERS, true);
+                }
+                catch (IOException ioe)
+                {
+                    throw (BuildException) SuppressableTask.newBuildException (m_task.getTaskName ()
+                        + ": I/O exception while processing input" , ioe, m_task.getLocation ()).fillInStackTrace ();
+                }
+            }
+            
+            return m_specs;
+        }
+    }
+    
+    // protected: .............................................................
+
+
+    protected static final String COMMA               = ",";
+    protected static final String COMMA_DELIMITERS    = COMMA + Strings.WHITE_SPACE;
+    protected static final String PATH_DELIMITERS     = COMMA.concat (File.pathSeparator);
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+    
+    private final Task m_task; // never null
+    private final List /* filterElement */ m_filterList; // never null
+    
+    private transient String [] m_specs;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/instr/instrTask.java b/ant/ant14/com/vladium/emma/instr/instrTask.java
new file mode 100644
index 0000000..30c9f3d
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/instr/instrTask.java
@@ -0,0 +1,181 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: instrTask.java,v 1.1.1.1.2.1 2004/07/08 10:52:12 vlad_r Exp $
+ */
+package com.vladium.emma.instr;
+
+import java.io.File;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.ant.FilterTask;
+import com.vladium.emma.ant.SuppressableTask;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class instrTask extends FilterTask
+{
+    // public: ................................................................
+
+
+    public static final class ModeAttribute extends EnumeratedAttribute
+    {
+        public String [] getValues ()
+        {
+            return VALUES;
+        }
+        
+        private static final String [] VALUES = new String [] {"copy", "overwrite", "fullcopy"};
+
+    } // end of nested class
+    
+    
+    public instrTask (final SuppressableTask parent)
+    {
+        super (parent);
+        
+        m_outMode = InstrProcessor.OutMode.OUT_MODE_COPY; // default
+    }
+    
+        
+    public void execute () throws BuildException
+    {
+        if (isEnabled ())
+        {
+            if (m_instrpath == null)
+                throw (BuildException) newBuildException (getTaskName ()
+                    + ": instrumentation path must be specified", location).fillInStackTrace ();
+ 
+            if ((m_outMode != InstrProcessor.OutMode.OUT_MODE_OVERWRITE) && (m_outDir == null))
+                throw (BuildException) newBuildException (getTaskName ()
+                    + ": output directory must be specified for '" + m_outMode + "' output mode", location).fillInStackTrace ();
+            
+            InstrProcessor processor = InstrProcessor.create ();
+                
+            $assert.ASSERT (m_instrpath != null, "m_instrpath not set");
+            processor.setInstrPath (m_instrpath.list (), true); // TODO: an option to set 'canonical'?
+            // processor.setDependsMode ()
+            processor.setInclExclFilter (getFilterSpecs ());
+            $assert.ASSERT (m_outMode != null, "m_outMode not set");
+            processor.setOutMode (m_outMode);
+            processor.setInstrOutDir (m_outDir != null ? m_outDir.getAbsolutePath () : null);
+            processor.setMetaOutFile (m_outFile != null ? m_outFile.getAbsolutePath () : null);
+            processor.setMetaOutMerge (m_outFileMerge);
+            processor.setPropertyOverrides (getTaskSettings ());
+            
+            processor.run ();
+        }
+    }
+    
+       
+    // instrpath attribute/element:
+    
+    public void setInstrpath (final Path path)
+    {
+        if (m_instrpath == null)
+            m_instrpath = path;
+        else
+            m_instrpath.append (path);
+    }
+    
+    public void setInstrpathRef (final Reference ref)
+    {
+        createInstrpath ().setRefid (ref);
+    }
+    
+    public Path createInstrpath ()
+    {
+        if (m_instrpath == null)
+            m_instrpath = new Path (project);
+        
+        return m_instrpath.createPath ();
+    }
+    
+    
+    // outdir|destdir attribute:
+    
+    public void setOutdir (final File dir)
+    {
+        if (m_outDir != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": outdir|destdir attribute already set", location).fillInStackTrace ();
+            
+        m_outDir = dir;
+    }
+    
+    public void setDestdir (final File dir)
+    {
+        if (m_outDir != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": outdir|destdir attribute already set", location).fillInStackTrace ();
+        
+        m_outDir = dir;
+    }
+    
+
+    // metadatafile|outfile attribute:
+
+    public void setMetadatafile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": metadata file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+    
+    public void setOutfile (final File file)
+    {
+        if (m_outFile != null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": metadata file attribute already set", location).fillInStackTrace ();
+            
+        m_outFile = file;
+    }
+    
+    // merge attribute:
+     
+    public void setMerge (final boolean merge)
+    {
+        m_outFileMerge = merge ? Boolean.TRUE : Boolean.FALSE;       
+    }
+    
+    
+    // mode attribute:
+    
+    public void setMode (final ModeAttribute mode)
+    {        
+        final InstrProcessor.OutMode outMode = InstrProcessor.OutMode.nameToMode (mode.getValue ());
+        if (outMode == null)
+            throw (BuildException) newBuildException (getTaskName ()
+                + ": invalid output mode: " + mode.getValue (), location).fillInStackTrace ();
+        
+        m_outMode = outMode;
+    }
+    
+    // protected: .............................................................
+        
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+        
+    private Path m_instrpath;
+    private InstrProcessor.OutMode m_outMode;
+    private File m_outDir;
+    private File m_outFile;
+    private Boolean m_outFileMerge;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/report/IReportEnums.java b/ant/ant14/com/vladium/emma/report/IReportEnums.java
new file mode 100644
index 0000000..7b81457
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/report/IReportEnums.java
@@ -0,0 +1,96 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IReportEnums.java,v 1.1.1.1 2004/05/09 16:57:27 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import com.vladium.emma.report.IReportProperties;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IReportEnums
+{
+    // public: ................................................................
+
+
+    final class TypeAttribute extends EnumeratedAttribute
+    {
+        public String [] getValues ()
+        {
+            return VALUES;
+        }
+        
+        // TODO: keep this enum in a centralized location
+        private static final String [] VALUES = new String []
+        {
+            "txt",
+            "html",
+            "xml",
+        };
+
+    } // end of nested class
+    
+
+    final class DepthAttribute extends EnumeratedAttribute
+    {
+        public String [] getValues ()
+        {
+            return VALUES;
+        }
+        
+        // TODO: keep this enum in a centralized location
+        private static final String [] VALUES = new String []
+        {
+            IReportProperties.DEPTH_ALL,
+            IReportProperties.DEPTH_PACKAGE,
+            IReportProperties.DEPTH_SRCFILE,
+            IReportProperties.DEPTH_CLASS,
+            IReportProperties.DEPTH_METHOD,
+        };
+
+    } // end of nested class
+    
+
+    final class ViewTypeAttribute extends EnumeratedAttribute
+    {
+        public String [] getValues ()
+        {
+            return VALUES;
+        }
+        
+        // TODO: keep this enum in a centralized location
+        private static final String [] VALUES = new String []
+        {
+            IReportProperties.SRC_VIEW,
+            IReportProperties.CLS_VIEW,
+        };
+
+    } // end of nested class
+    
+    
+    static final class UnitsTypeAttribute extends EnumeratedAttribute
+    {
+        public String [] getValues ()
+        {
+            return VALUES;
+        }
+        
+        // TODO: keep this enum in a centralized location
+        private static final String [] VALUES = new String []
+        {
+            IReportProperties.INSTR_UNITS,
+            IReportProperties.COUNT_UNITS,
+        };
+
+    } // end of nested class
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/report/ReportCfg.java b/ant/ant14/com/vladium/emma/report/ReportCfg.java
new file mode 100644
index 0000000..71a01b4
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/report/ReportCfg.java
@@ -0,0 +1,424 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportCfg.java,v 1.1.1.1.2.1 2004/07/08 10:52:11 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import com.vladium.util.IConstants;
+import com.vladium.util.IProperties;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.ant.PropertyElement;
+import com.vladium.emma.ant.SuppressableTask;
+import com.vladium.emma.report.IReportEnums.DepthAttribute;
+import com.vladium.emma.report.IReportEnums.UnitsTypeAttribute;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+
+// ----------------------------------------------------------------------------
+/**
+ * ReportCfg is a container for report type {@link ReportCfg.Element}s that are
+ * in turn containers for all properties that could be set on a &lt;report&gt;
+ * report type configurator (&lt;txt&gt;, &lt;html&gt;, etc). The elements provide
+ * the ability for report properties to be set either via the generic &lt;property&gt;
+ * nested elements or dedicated attributes. Potential conflicts between the same
+ * conceptual property being set via an attribute and a nested element are resolved
+ * by making dedicated attributes higher priority.<P>
+ * 
+ * Note that ReportCfg does not handle any non-report related properties.
+ * This can be done via {@link com.vladium.emma.ant.GenericCfg}. It is also the
+ * parent's responsibility to merge any inherited report properties with
+ * ReportCfg settings. 
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class ReportCfg implements IReportProperties
+{
+    // public: ................................................................
+
+
+    public static abstract class Element implements IReportEnums, IReportProperties
+    {
+        public void setUnits (final UnitsTypeAttribute units)
+        {
+            m_settings.setProperty (m_prefix.concat (UNITS_TYPE), units.getValue ());
+        }
+        
+        public void setDepth (final DepthAttribute depth)
+        {
+            m_settings.setProperty (m_prefix.concat (DEPTH), depth.getValue ());
+        }
+        
+        public void setColumns (final String columns)
+        {
+            m_settings.setProperty (m_prefix.concat (COLUMNS), columns);
+        }
+        
+        public void setSort (final String sort)
+        {
+            m_settings.setProperty (m_prefix.concat (SORT), sort);
+        }
+        
+        public void setMetrics (final String metrics)
+        {
+            m_settings.setProperty (m_prefix.concat (METRICS), metrics);
+        }
+        
+        // not supported anymore:
+        
+//        public void setOutdir (final File dir)
+//        {
+//            // TODO: does ANT resolve files relative to current JVM dir or ${basedir}?
+//            m_settings.setProperty (m_prefix.concat (OUT_DIR), dir.getAbsolutePath ());
+//        }
+        
+        public void setOutfile (final String fileName)
+        {
+            m_settings.setProperty (m_prefix.concat (OUT_FILE), fileName);
+        }
+        
+        public void setEncoding (final String encoding)
+        {
+            m_settings.setProperty (m_prefix.concat (OUT_ENCODING), encoding);
+        }
+        
+        // generic property element [don't doc this publicly]:
+        
+        public PropertyElement createProperty ()
+        {
+            // TODO: error out on conficting duplicate settings
+            
+            final PropertyElement property = new PropertyElement ();
+            m_genericSettings.add (property);
+            
+            return property;
+        }
+        
+        protected abstract String getType ();
+        
+
+        Element (final Task task, final IProperties settings)
+        {
+            if (task == null)
+                throw new IllegalArgumentException ("null input: task");
+            if (settings == null)
+                throw new IllegalArgumentException ("null input: settings");
+            
+            m_task = task;
+            m_settings = settings;
+            
+            m_prefix = PREFIX.concat (getType ()).concat (".");
+            
+            m_genericSettings = new ArrayList ();
+        }
+        
+        
+        void processGenericSettings ()
+        {
+            for (Iterator i = m_genericSettings.iterator (); i.hasNext (); )
+            {
+                final PropertyElement property = (PropertyElement) i.next ();
+                
+                final String name = property.getName ();
+                final String value = property.getValue () != null ? property.getValue () : "";
+                
+                if (name != null)
+                {
+                    final String prefixedName = m_prefix.concat (name);
+                    
+                    // generically named settings don't override report named settings:
+                    
+                    if (! m_settings.isOverridden (prefixedName))
+                        m_settings.setProperty (prefixedName, value);
+                }
+            }
+        }
+        
+        
+        protected final Task m_task; // never null
+        protected final String m_prefix; // never null
+        protected final IProperties m_settings; // never null
+        protected final List /* PropertyElement */ m_genericSettings; // never null
+        
+    } // end of nested class
+
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    public static class Element_HTML extends Element
+    {
+        protected final String getType ()
+        {
+            return TYPE;
+        }
+        
+        Element_HTML (final Task task, final IProperties settings)
+        {
+            super (task, settings);
+        }
+        
+        
+        static final String TYPE = "html";
+        
+    } // end of nested class
+    
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    
+    public static class Element_TXT extends Element
+    {
+        protected final String getType ()
+        {
+            return TYPE;
+        }
+        
+        Element_TXT (final Task task, final IProperties settings)
+        {
+            super (task, settings);
+        }
+        
+        
+        static final String TYPE = "txt";
+        
+    } // end of nested class
+    
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    
+    public static class Element_XML extends Element
+    {
+        protected final String getType ()
+        {
+            return TYPE;
+        }
+        
+        Element_XML (final Task task, final IProperties settings)
+        {
+            super (task, settings);
+        }
+        
+        
+        static final String TYPE = "xml";
+        
+    } // end of nested class
+    
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    
+    
+    public ReportCfg (final Project project, final Task task)
+    {
+        m_project = project;
+        m_task = task;
+        
+        m_reportTypes = new ArrayList (4);
+        m_cfgList = new ArrayList (4);
+        m_settings = EMMAProperties.wrap (new Properties ());
+    }
+    
+    public Path getSourcepath ()
+    {
+        return m_srcpath;
+    }
+    
+    public String [] getReportTypes ()
+    {
+        final BuildException failure = getFailure ();
+        
+        if (failure != null)
+            throw failure;
+        else
+        {
+            if (m_reportTypes.isEmpty ())
+                return IConstants.EMPTY_STRING_ARRAY;
+            else
+            {
+                final String [] result = new String [m_reportTypes.size ()];
+                m_reportTypes.toArray (result);
+                
+                return result;
+            }
+        }
+    }
+    
+    public IProperties getReportSettings ()
+    {
+        final BuildException failure = getFailure ();
+        
+        if (failure != null)
+            throw failure;
+        else
+        {
+            if (! m_processed)
+            {
+                // collect all nested elements' generic settins into m_settings:
+                
+                for (Iterator i = m_cfgList.iterator (); i.hasNext (); )
+                {
+                    final Element cfg = (Element) i.next ();
+                    cfg.processGenericSettings ();
+                }
+                
+                m_processed = true;
+            }
+            
+            return m_settings; // no clone
+        }
+    }
+    
+    
+    // sourcepath attribute/element:
+    
+    public void setSourcepath (final Path path)
+    {
+        if (m_srcpath == null)
+            m_srcpath = path;
+        else
+            m_srcpath.append (path);
+    }
+    
+    public void setSourcepathRef (final Reference ref)
+    {
+        createSourcepath ().setRefid (ref);
+    }
+    
+    public Path createSourcepath ()
+    {
+        if (m_srcpath == null)
+            m_srcpath = new Path (m_project);
+        
+        return m_srcpath.createPath ();
+    }
+    
+    
+    // generator elements:
+    
+    public Element_TXT createTxt ()
+    {
+        return (Element_TXT) addCfgElement (Element_TXT.TYPE,
+                                                     new Element_TXT (m_task, m_settings));
+    }
+    
+    public Element_HTML createHtml ()
+    {
+        return (Element_HTML) addCfgElement (Element_HTML.TYPE,
+                                                      new Element_HTML (m_task, m_settings));
+    }
+    
+    public Element_XML createXml ()
+    {
+        return (Element_XML) addCfgElement (Element_XML.TYPE,
+                                                     new Element_XML (m_task, m_settings));
+    }
+    
+    
+    // report properties [defaults for all report types]:
+
+    public void setUnits (final UnitsTypeAttribute units)
+    {
+        m_settings.setProperty (PREFIX.concat (UNITS_TYPE), units.getValue ());
+    }    
+
+    public void setDepth (final DepthAttribute depth)
+    {
+        m_settings.setProperty (PREFIX.concat (DEPTH), depth.getValue ());
+    }
+    
+    public void setColumns (final String columns)
+    {
+        m_settings.setProperty (PREFIX.concat (COLUMNS), columns);
+    }
+    
+    public void setSort (final String sort)
+    {
+        m_settings.setProperty (PREFIX.concat (SORT), sort);
+    }
+    
+    public void setMetrics (final String metrics)
+    {
+        m_settings.setProperty (PREFIX.concat (METRICS), metrics);
+    }
+
+    // not supported anymore:
+    
+//    public void setOutdir (final File dir)
+//    {
+//        // TODO: does ANT resolve files relative to current JVM dir or ${basedir}?
+//        m_settings.setProperty (PREFIX.concat (OUT_DIR), dir.getAbsolutePath ());
+//    }
+//    
+//    public void setDestdir (final File dir)
+//    {
+//        // TODO: does ANT resolve files relative to current JVM dir or ${basedir}?
+//        m_settings.setProperty (PREFIX.concat (OUT_DIR), dir.getAbsolutePath ());
+//    }
+    
+    public void setOutfile (final String fileName)
+    {
+        m_settings.setProperty (PREFIX.concat (OUT_FILE), fileName);
+    }
+    
+    public void setEncoding (final String encoding)
+    {
+        m_settings.setProperty (PREFIX.concat (OUT_ENCODING), encoding);
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected Element addCfgElement (final String type, final Element cfg)
+    {
+        if (m_reportTypes.contains (type))
+        {
+            setFailure ((BuildException) SuppressableTask.newBuildException (m_task.getTaskName ()
+                + ": duplicate configuration for report type [" + type + "]" ,
+                m_task.getLocation ()).fillInStackTrace ());
+        }
+        else
+        {
+            m_reportTypes.add (type);
+            m_cfgList.add (cfg);
+        }
+        
+        return cfg;
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private void setFailure (final BuildException failure)
+    {
+        if (m_settingsFailure == null) m_settingsFailure = failure; // record the first one only
+    }
+    
+    private BuildException getFailure ()
+    {
+        return m_settingsFailure;
+    }
+    
+    
+    private final Project m_project;
+    private final Task m_task;
+    
+    private final List /* report type:String */ m_reportTypes; // using a list to keep the generation order same as configuration
+    private final List /* Element */ m_cfgList;
+    private final IProperties m_settings; // never null
+    
+    private Path m_srcpath;
+    
+    private transient BuildException m_settingsFailure; // can be null
+    private transient boolean m_processed;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant14/com/vladium/emma/report/reportTask.java b/ant/ant14/com/vladium/emma/report/reportTask.java
new file mode 100644
index 0000000..b726753
--- /dev/null
+++ b/ant/ant14/com/vladium/emma/report/reportTask.java
@@ -0,0 +1,179 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: reportTask.java,v 1.1.1.1.2.1 2004/07/08 10:52:11 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+
+import com.vladium.util.IProperties;
+import com.vladium.emma.ant.FileTask;
+import com.vladium.emma.ant.SuppressableTask;
+import com.vladium.emma.report.ReportCfg.Element_HTML;
+import com.vladium.emma.report.ReportCfg.Element_TXT;
+import com.vladium.emma.report.ReportCfg.Element_XML;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class reportTask extends FileTask implements IReportProperties, IReportEnums
+{
+    public reportTask (final SuppressableTask parent)
+    {
+        super (parent);
+    }
+    
+    public void init () throws BuildException
+    {
+        super.init ();
+        
+        m_reportCfg = new ReportCfg (getProject (), this);
+    }
+
+    
+    public void execute () throws BuildException
+    {
+        if (isEnabled ())
+        {
+            final String [] reportTypes = m_reportCfg.getReportTypes ();
+            
+            if ((reportTypes == null) || (reportTypes.length == 0)) // no "txt" default for report processor
+                throw (BuildException) newBuildException (getTaskName ()
+                    + ": no report types specified: provide at least one of <txt>, <html>, <xml> nested elements", location).fillInStackTrace ();
+
+            String [] files = getDataPath (true);
+            if ((files == null) || (files.length == 0))
+                throw (BuildException) newBuildException (getTaskName ()
+                    + ": no valid input data files have been specified", location).fillInStackTrace ();
+
+            final Path srcpath = m_reportCfg.getSourcepath ();
+            
+            // combine report and all generic settings:
+            final IProperties settings; 
+            {
+                final IProperties taskSettings = getTaskSettings ();
+                final IProperties reportSettings = m_reportCfg.getReportSettings ();
+                
+                // named report settings override generic named settings and file
+                // settings have lower priority than any explicitly named overrides:
+                settings = IProperties.Factory.combine (reportSettings, taskSettings);
+            }
+
+            final ReportProcessor processor = ReportProcessor.create ();
+            
+            processor.setDataPath (files); files = null;
+            processor.setSourcePath (srcpath != null ? srcpath.list () : null);
+            processor.setReportTypes (reportTypes);
+            processor.setPropertyOverrides (settings);        
+            
+            processor.run ();
+        }
+    }
+
+    
+    // sourcepath attribute/element:
+    
+    public void setSourcepath (final Path path)
+    {
+        m_reportCfg.setSourcepath (path);
+    }
+    
+    public void setSourcepathRef (final Reference ref)
+    {
+        m_reportCfg.setSourcepathRef (ref);
+    }
+    
+    public Path createSourcepath ()
+    {
+        return m_reportCfg.createSourcepath ();
+    }
+    
+    
+    // generator elements:
+    
+    public Element_TXT createTxt ()
+    {
+        return m_reportCfg.createTxt ();
+    }
+    
+    public Element_HTML createHtml ()
+    {
+        return m_reportCfg.createHtml ();
+    }
+    
+    public Element_XML createXml ()
+    {
+        return m_reportCfg.createXml ();
+    }
+    
+    
+    // report properties [defaults for all report types]:
+
+    public void setUnits (final UnitsTypeAttribute units)
+    {
+        m_reportCfg.setUnits (units);
+    }    
+
+    public void setDepth (final DepthAttribute depth)
+    {
+        m_reportCfg.setDepth (depth);
+    }
+    
+    public void setColumns (final String columns)
+    {
+        m_reportCfg.setColumns (columns);
+    }
+    
+    public void setSort (final String sort)
+    {
+        m_reportCfg.setSort (sort);
+    }
+    
+    public void setMetrics (final String metrics)
+    {
+        m_reportCfg.setMetrics (metrics);
+    }
+
+    // not supported anymore:
+    
+//    public void setOutdir (final File dir)
+//    {
+//        m_reportCfg.setOutdir (dir);
+//    }
+//    
+//    public void setDestdir (final File dir)
+//    {
+//        m_reportCfg.setDestdir (dir);
+//    }
+    
+    // should not be set at the global level:
+    
+//    public void setOutfile (final String fileName)
+//    {
+//        m_reportCfg.setOutfile (fileName);
+//    }
+    
+    public void setEncoding (final String encoding)
+    {
+        m_reportCfg.setEncoding (encoding);
+    }
+        
+    // protected: .............................................................
+        
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+    
+    private ReportCfg m_reportCfg;   
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant15/com/vladium/emma/ant/IANTVersion.java b/ant/ant15/com/vladium/emma/ant/IANTVersion.java
new file mode 100644
index 0000000..2a8458d
--- /dev/null
+++ b/ant/ant15/com/vladium/emma/ant/IANTVersion.java
@@ -0,0 +1,69 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IANTVersion.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.tools.ant.types.FileSet;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+interface IANTVersion
+{
+    // public: ................................................................
+    
+    /** 'true' iff the current runtime version is 1.2 or later */
+    boolean ANT_1_5_PLUS = _ANTVersion._ANT_1_5_PLUS; // static final but not inlinable
+    
+    
+    abstract class _ANTVersion
+    {
+        static final boolean _ANT_1_5_PLUS; // set in <clinit>
+        
+        private _ANTVersion () {  /* prevent subclassing */ }
+    
+        static
+        {
+            boolean temp = true;
+            try
+            {
+                final Method m = FileSet.class.getMethod ("setFile", new Class [] { File.class });
+                
+                // [assertion: 'm' is public]
+                
+                final int modifiers = m.getModifiers ();
+                if ((modifiers & Modifier.STATIC) != 0)
+                    temp = false;
+            }
+            catch (NoSuchMethodException nsme)
+            {
+                temp = false;
+            }
+            catch (SecurityException se)
+            {
+                temp = false;
+            }
+            catch (Throwable t)
+            {
+                t.printStackTrace (System.out);
+                temp = false;
+            }
+            
+            _ANT_1_5_PLUS = temp;
+        }
+
+    } // end of nested class
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/ant15/com/vladium/emma/ant/XFileSet.java b/ant/ant15/com/vladium/emma/ant/XFileSet.java
new file mode 100644
index 0000000..49e0bb2
--- /dev/null
+++ b/ant/ant15/com/vladium/emma/ant/XFileSet.java
@@ -0,0 +1,66 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: XFileSet.java,v 1.1.1.1 2004/05/09 16:57:28 vlad_r Exp $
+ */
+package com.vladium.emma.ant;
+
+import java.io.File;
+
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.PatternSet;
+
+// ----------------------------------------------------------------------------
+/**
+ * An extension of ANT's stock FileSet that adds the convenience of specifying
+ * a single 'file' attribute
+ * 
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+final class XFileSet extends FileSet
+{
+    // public: ................................................................
+    
+    
+    public XFileSet ()
+    {
+        super ();
+    }
+    
+    public XFileSet (final FileSet fileset)
+    {
+        super (fileset);
+    }
+    
+    
+    // 'file' attribute:
+    public void setFile (final File file)
+    {
+        if (IANTVersion.ANT_1_5_PLUS)
+        {
+            super.setFile (file);
+        }
+        else
+        {
+            if (isReference ()) throw tooManyAttributes ();
+        
+            final File parent = file.getParentFile ();
+            if (parent != null) setDir (parent);
+    
+            final PatternSet.NameEntry include = createInclude ();
+            include.setName (file.getName ());
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+        
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/ant/data/placeholder.exclude b/ant/data/placeholder.exclude
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ant/data/placeholder.exclude
diff --git a/ant/res/placeholder.exclude b/ant/res/placeholder.exclude
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ant/res/placeholder.exclude
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..0c98e24
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,514 @@
+<?xml version="1.0"?>
+<!-- ============= [build file for ANT v1.5.x] ====================== -->
+<!DOCTYPE project
+[
+    <!ENTITY dependencies SYSTEM "dependencies.xml" >
+]>
+
+<project name="emma" default="build" basedir="." >
+  <!-- $Id: build.xml,v 1.2.2.3 2005/06/13 00:20:55 vlad_r Exp $ --> 
+  <property name="app.short.name" value="${ant.project.name}" />
+  <property name="app.project.start.date" value="2001/02/01" />
+
+  <!-- TODO: build log --> 
+
+  <!-- ============================================================== -->
+
+  <!-- pull in the OS environment variables; an OS env variable VAR can be used as ${env.VAR}: -->
+  <property environment="env" />
+
+  <!-- ============================================================== -->
+  <!-- build branch properties and local customization overrides: -->
+
+  <!-- first, load the official build settings file: -->
+  <property file="build.properties" />
+  
+  <!-- next, load the CVS-versioned release/branch properties file: -->
+  <property file="release.properties" />
+
+  <!-- next, load an optional global properties file: -->
+  <property file="${env.ANT_BLDENV}/ant.properties" />
+
+  <!-- ============================================================== -->
+  <!-- global compiler settings: -->
+
+  <property name="build.debug" value="on" /> <!-- default for a release build is "on" -->
+  <property name="build.deprecation" value="off" />
+  <property name="build.compiler" value="modern" />
+  <property name="build.sysclasspath" value="ignore" />
+  <property name="build.target" value="1.2" /> <!-- all .class files are stamped as compatible with 1.2 JVMs: -->
+
+  <!-- ============================================================== -->
+  <!-- main/default build target: -->
+
+  <target name="build" depends="dist"
+          description="-> full build [without 'clean']"
+  />
+
+  <!-- helper build subtargets: -->
+
+  <target name="build.core.compile" depends="core.compile"
+          description="-> re-compiles updated core sources [no 'clean']"
+  />
+  <target name="build.core.package" depends="core.package"
+          description="-> re-packages core classes [no 'clean']"
+  />
+
+  <!-- build modifier targets: -->
+
+  <target name="official"
+          description="-> switches 'build' into official, CVS-aware mode" >
+    <property name="build.is.official" value="yes" />
+  </target>
+
+  <target name="release"
+          description="-> [RELEASE]" >
+    <property name="build.is.on.a.branch" value="yes" />
+    <property name="app.build.release.tag" value="" /> <!-- no tag means official release -->
+    <property name="file.release.prefix" value="" />
+    <property name="cvs.ready.label.prefix" value="RELEASE" />
+  </target>
+
+  <target name="stable"
+          description="-> [STABLE]" >
+    <property name="app.build.release.tag" value=" (stable)" />
+    <property name="file.release.prefix" value="-stable" />
+    <property name="cvs.ready.label.prefix" value="STABLE" />
+  </target>
+
+  <!-- ============================================================== -->
+  <!-- init targets: -->
+  <!-- ============================================================== -->
+
+  <!-- static (checked-in) build structure: -->
+  <property file="module.dirs.properties" />
+
+  <!-- work (created by the build) build structure: -->
+  <property file="work.dirs.properties" />
+
+  <target name="init.work.dirs" >
+    <mkdir dir="${out.dir}" />
+    <mkdir dir="${dist.dir}" />
+    <mkdir dir="${release.dir}" />
+    <mkdir dir="${temp.dir}" />
+
+    <mkdir dir="${tools.classes.out.dir}" />
+
+    <mkdir dir="${core.classes.out.dir}" />
+    <mkdir dir="${core.srcgen.out.dir}" />
+    <mkdir dir="${core.res.out.dir}" />
+
+    <mkdir dir="${ant.classes.out.dir}" />
+  </target>
+
+  <target name="init" depends="init.work.dirs">
+    <!-- NOTE: the official build machine sets these explicitly,
+         these unofficial defaults are provided for private builds only: -->
+    <property name="build.target.j2se.14.home" value="${java.home}" />
+    <property name="build.target.j2se.12.home" value="${build.target.j2se.14.home}" />
+    <property name="build.target.j2se.13.home" value="${build.target.j2se.14.home}" />
+
+    <property name="build.target.ant.15.home" value="${env.ANT_HOME}" />
+    <property name="build.target.ant.14.home" value="${build.target.ant.15.home}" />
+
+    
+    <!-- verify that we have J2SE 1.4+: -->
+    <available property="j2se.is.14+" classname="java.lang.CharSequence" />
+    <fail unless="j2se.is.14+">location [${build.target.j2se.14.home}] does not specify J2SE 1.4+</fail>
+
+    <available file="${temp.dir}/.ready.build.stamp" type="file" property="build.is.dirty" value="yes" />
+
+    <!-- include dependencies.xml (a dynamic include by design): -->
+    &dependencies;
+
+    <echo message="ANT version: ${ant.version}" />
+    <echo message="JDK version: ${ant.java.version}" />
+    <echo message="build debug flag: ${build.debug}" />
+  </target>
+
+  <!-- ============================================================== -->
+  <!-- support targets: -->
+  <!-- ============================================================== -->
+
+  <target name="clean" depends="clean.out, clean.dist" description="-> complete clean (output + distribution)" />
+
+  <target name="clean.out" description="-> output clean" >
+    <delete dir="${out.dir}" />
+  </target>
+
+  <target name="clean.dist" description="-> distribution clean" >
+    <delete dir="${dist.dir}"/>
+    <delete dir="${release.dir}"/>
+  </target>
+
+
+  <target name="timestamp" depends="init, -timestamp.1, -timestamp.2, -timestamp.3, -timestamp.4, -timestamp.5">
+  </target>
+
+  <target name="-timestamp.1" depends="init" unless="build.is.dirty" >
+    <java classname="com.vladium.util.version.VersionStampTool"
+          classpathref="lib.internal.classpath"
+          output="${temp.dir}/.ready.build.stamp"
+          append="no"
+    >
+        <arg value="-start" />
+        <arg value="${app.project.start.date}" />
+        <arg value="-format" />
+        <arg value="~F~" />
+    </java>
+    <java classname="com.vladium.util.version.VersionStampTool"
+          classpathref="lib.internal.classpath"
+          output="${temp.dir}/timestamp.properties"
+          append="no"
+    >
+        <arg value="-start" />
+        <arg value="${app.project.start.date}" />
+        <arg value="-fend" />
+        <arg value="${temp.dir}/.ready.build.stamp" />
+        <arg value="-format" />
+        <arg value="app.build.id=${app.build.id.format}\napp.build.date=${app.build.date.format}" />
+    </java>
+    <!-- TODO: this is ugly and breaks private builds (constant appending is bad any way): -->
+    <!-- <echo message="file.release.prefix=${file.release.prefix}" file="${temp.dir}/timestamp.properties" append="yes" /> -->
+  </target>
+
+  <target name="-timestamp.2" depends="init" >
+    <property file="${temp.dir}/timestamp.properties" />
+
+    <!-- private/Eclipse build defaults: -->
+    <property name="app.build.release.tag" value=" (unsupported private build)" />
+    <property name="app.bug.report.link" value="this private build is unsupported" />
+    <property name="file.release.prefix" value="-private" />
+
+    <property name="file.release.label" value="${app.major.version}.${app.minor.version}.${app.build.id}" />
+    <echo message="build id: ${file.release.label}${app.build.release.tag}" />
+  </target>
+
+  <target name="-timestamp.2a" depends="init" if="build.is.on.a.branch" >
+    <property name="cvs.branch.label" value="BRANCH_${app.major.version}_${app.minor.version}" />
+  </target>
+
+  <target name="-timestamp.3" depends="init, -timestamp.2a" if="build.is.official" >
+    <property name="cvs.ready.label" value="${cvs.ready.label.prefix}_${app.major.version}_${app.minor.version}_${app.build.id}" />
+    <property name="cvs.branch.label" value="HEAD" /> <!-- default to a trunk/stable build -->
+    <echo message="CVS branch label: ${cvs.branch.label}" />
+    <echo message="release version (CVS ready label): ${cvs.ready.label}" />
+  </target>
+
+  <target name="-timestamp.4" depends="init, build.cvs.ready" if="build.is.official" >
+  </target>
+
+  <target name="-timestamp.5" depends="init" unless="build.is.dirty" >
+    <copy overwrite="yes" todir="${core.srcgen.out.dir}" >
+      <fileset dir="${core.data.dir}" includes="**/*.java" />
+      <filterset>
+        <filter token="APP_MAJOR_VERSION" value='*/ ${app.major.version}; // ' />
+        <filter token="APP_MINOR_VERSION" value='*/ ${app.minor.version}; // ' />
+        <filter token="APP_BUILD_ID" value='*/ ${app.build.id}; // ' />
+        <filter token="APP_BUILD_RELEASE_TAG" value='*/ "${app.build.release.tag}"; // ' />
+        <filter token="APP_BUILD_DATE" value='*/ "${app.build.date}"; // ' />
+        <filter token="APP_BUG_REPORT_LINK" value='*/ "${app.bug.report.link}"; // ' />
+        <filter token="APP_HOME_SITE_LINK" value='*/ "${app.home.site.link}"; // ' />
+      </filterset>
+    </copy>
+    <copy overwrite="yes" file="${core.data.dir}/MANIFEST.MF" todir="${temp.dir}" >
+      <filterset>
+        <filter token="JDK_ID" value="JDK_${ant.java.version}" />
+        <filter token="BUILD_USER_NAME" value="${user.name} on ${os.name}:${os.version}:${os.arch}" />
+        
+        <filter token="APP_NAME" value="${app.short.name}" />
+        <filter token="APP_MAJOR_VERSION" value="${app.major.version}" />
+        <filter token="APP_MINOR_VERSION" value="${app.minor.version}" />
+        <filter token="APP_BUILD_ID" value="${app.build.id}" />
+        <filter token="APP_BUILD_RELEASE_TAG" value="${app.build.release.tag}" />
+        <filter token="APP_BUILD_DATE" value="${app.build.date}" />
+        <filter token="APP_BUG_REPORT_LINK" value="${app.bug.report.link}" />
+        <filter token="APP_HOME_SITE_LINK" value="${app.home.site.link}" />
+      </filterset>
+    </copy>
+  </target>
+
+  <target name="-pre-compile" depends="init, timestamp" >
+  </target>
+
+  <!-- ============================================================== -->
+  <!-- compilation targets: -->
+  <!-- ============================================================== -->
+
+  <target name="compile" depends="init, core.compile, ant.compile, tools.compile" />
+
+  <!-- ========================== -->
+  <!-- core module: -->
+
+  <target name="core.bootstrap.compile" depends="init, -pre-compile" >
+    <javac destdir="${core.classes.out.dir}"
+           debug="${build.debug}"
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.12.classpath"
+           extdirs="${ext.j2se.12.classpath}"
+           classpathref="core.classpath"
+    >
+      <!-- include IAppVersion.java: -->
+      <src path="${core.srcgen.out.dir}" />
+
+      <!-- classes used by core.src.java12.dir sources: -->
+      <src path="${core.src.java12.dir}" />
+      <include name="**/util/Property.java" />
+      <include name="**/IAppConstants.java" />
+    </javac>
+  </target>
+
+  <target name="core.java14.compile" depends="init, core.bootstrap.compile" >
+    <javac destdir="${core.classes.out.dir}"
+           debug="${build.debug}"
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.14.classpath"
+           extdirs="${ext.j2se.14.classpath}"
+           classpathref="core.classpath"
+    >
+      <src path="${core.src.java14.dir}" />
+    </javac>
+  </target>
+
+  <target name="core.java13.compile" depends="init, core.java14.compile" >
+    <javac destdir="${core.classes.out.dir}"
+           debug="${build.debug}"
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.13.classpath"
+           extdirs="${ext.j2se.13.classpath}"
+           classpathref="core.classpath"
+    >
+      <src path="${core.src.java13.dir}" />
+    </javac>
+  </target>
+
+  <!-- the bulk of core compilation: -->
+  <target name="core.compile" depends="init, core.java13.compile" >
+    <javac destdir="${core.classes.out.dir}"
+           debug="${build.debug}"
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.12.classpath"
+           extdirs="${ext.j2se.12.classpath}"
+           classpathref="core.classpath"
+    >
+      <src path="${core.src.java12.dir}" />
+      <exclude name="**/util/Property.java" />
+      <exclude name="**/IAppConstants.java" />
+    </javac>
+  </target>
+
+  <!-- ========================== -->
+  <!-- tools module: -->
+
+  <target name="tools.compile" depends="init, core.compile" >
+    <javac destdir="${tools.classes.out.dir}"
+           debug="${build.debug}"	
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.14.classpath"
+           extdirs="${ext.j2se.14.classpath}"
+           classpathref="tools.classpath"
+    >
+      <src path="${tools.src.dir}" />
+    </javac>
+  </target>
+
+  <!-- ========================== -->
+  <!-- ant module: -->
+
+  <target name="ant.15.compile" depends="init, core.compile" >
+    <javac destdir="${ant.classes.out.dir}"
+           debug="${build.debug}"
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.12.classpath"
+           extdirs="${ext.j2se.12.classpath}"
+           classpathref="ant.15.classpath"
+    >
+      <src path="${ant.src.ant15.dir}" />
+    </javac>
+  </target>
+
+  <!-- the bulk of ant compilation: -->
+  <target name="ant.compile" depends="init, core.compile, ant.15.compile" >
+    <javac destdir="${ant.classes.out.dir}"
+           debug="${build.debug}"
+           deprecation="${build.deprecation}"
+           target="${build.target}"
+           bootclasspathref="boot.j2se.12.classpath"
+           extdirs="${ext.j2se.12.classpath}"
+           classpathref="ant.14.classpath"
+    >
+      <src path="${ant.src.ant14.dir}" />
+    </javac>
+  </target>
+
+
+  <!-- ============================================================== -->
+  <!-- packaging targets: -->
+  <!-- ============================================================== -->
+
+  <target name="package" depends="init, core.package, ant.package" />
+
+  <!-- ========================== -->
+  <!-- run tools: -->
+  
+  <target name="rtclosure.gen" depends="init, compile" >
+    <property name="rtclosure.resource" value="com/vladium/emma/rt/RTExitHook.closure" />
+    <property name="rtclosure.classpath" refid="core.classpath" />
+
+    <java classname="com.vladium.tools.ClassDep"
+          classpathref="tools.classpath"
+    >
+      <arg value="${rtclosure.classpath}" />
+      <arg value="${core.res.out.dir}/${rtclosure.resource}" />
+      <arg value="com.vladium.emma.rt.RTExitHook" />
+      <arg value="com.vladium.emma.data.CoverageData" />
+    </java>
+  </target>
+
+  <!-- ========================== -->
+  <!-- core module: -->
+
+  <target name="core.package"  depends="init, core.compile, rtclosure.gen" >
+    <jar jarfile="${out.dir}/${app.short.name}.jar"
+         manifest="${temp.dir}/MANIFEST.MF"
+         compress="true"
+         index="false"
+     >
+      <!-- classes: -->
+      <fileset dir="${core.classes.out.dir}" includes="**/*.class" />
+      <!-- classloader resources: -->
+      <fileset dir="${core.res.dir}" excludes="**/*.exclude" />
+      <fileset dir="${core.res.out.dir}" excludes="**/*.exclude" />
+      <manifest>
+        <attribute name="Main-Class" value="${app.short.name}run" />
+      </manifest>
+    </jar>
+  </target>
+
+  <!-- ========================== -->
+  <!-- ant module: -->
+
+  <target name="ant.package"  depends="init, ant.compile" >
+    <jar jarfile="${out.dir}/${app.short.name}_ant.jar"
+         manifest="${temp.dir}/MANIFEST.MF"
+         compress="true"
+         index="false"
+     >
+      <!-- classes: -->
+      <fileset dir="${ant.classes.out.dir}" includes="**/*.class" />
+      <!-- classloader resources: -->
+      <fileset dir="${ant.res.dir}" excludes="**/*.exclude" />
+      <manifest>
+        <attribute name="Main-Class" value="com.vladium.${app.short.name}.ANTMain" />
+      </manifest>
+    </jar>
+  </target>
+
+  <!-- ============================================================== -->
+  <!-- distribution targets: -->
+  <!-- ============================================================== -->
+
+  <target name="dist" depends="init, core.dist, ant.dist" />
+
+  <!-- ========================== -->
+  <!-- core module: -->
+  <target name="core.dist" depends="init, core.package" >
+    <copy file="${out.dir}/${app.short.name}.jar" todir="${dist.dir}" />
+  </target>
+
+  <!-- ========================== -->
+  <!-- ant module: -->
+  <target name="ant.dist" depends="init, ant.package" >
+    <copy file="${out.dir}/${app.short.name}_ant.jar" todir="${dist.dir}" />
+  </target>
+
+  <!-- ============================================================== -->
+  <!-- file release targets: -->
+  <!-- ============================================================== -->
+
+  <target name="publish.release" depends="init, release, publish.property.check, release.lib, release.full, release.src" >
+  </target>
+
+  <target name="publish.stable" depends="init, stable, publish.property.check, release.lib" >
+    <fail unless="file.release.label">need ${file.release.label}</fail>
+  </target>
+	
+  <target name="publish.property.check" depends="init" >
+    <fail unless="file.release.label">property ${file.release.label} must be set</fail>
+    <fail unless="docs.dist.dir">property ${docs.dist.dir} must be set</fail>
+    <property name="publish.ok" value="true" />
+  </target> 
+
+
+  <target name="release.lib" depends="init" if="publish.ok" >
+    <!-- lib distribution: -->
+    <zip destfile="${release.dir}/${app.short.name}${file.release.prefix}-${file.release.label}-lib.zip"
+         basedir="${dist.dir}"
+         includes="*.jar"
+    />
+  </target>
+
+  <target name="release.src" depends="init" if="publish.ok" >
+    <!-- source files: --> 
+    <zip destfile="${release.dir}/${app.short.name}-${file.release.label}-src.zip" >
+      <zipfileset prefix="${app.short.name}-${file.release.label}"
+                  dir="${basedir}"
+                  includes="${tools}/**, ${core}/**, ${ant}/**, ${lib}/**"
+                  excludes="*/CVS/*"
+      />
+      <!-- license, build script and instructions, etc: --> 
+      <zipfileset prefix="${app.short.name}-${file.release.label}"
+                  dir="${basedir}"
+                  includes="*.txt, *.xml, *.html, *.properties"
+                  excludes="build.properties"
+      />
+    </zip>
+  </target>
+
+  <target name="release.full" depends="init" if="publish.ok" >
+    <zip destfile="${release.dir}/${app.short.name}-${file.release.label}.zip" >
+      <!-- license: -->
+      <zipfileset prefix="${app.short.name}-${file.release.label}"
+                  dir="${basedir}/"
+                  includes="cpl*.html"
+      />
+      <!-- examples and related instructions: -->
+      <zipfileset prefix="${app.short.name}-${file.release.label}"
+                  dir="${basedir}/install/data"
+      />
+      <!-- lib distribution: -->
+      <zipfileset prefix="${app.short.name}-${file.release.label}/lib"
+                  dir="${dist.dir}"
+                  includes="*.jar"
+      />
+      <!-- doc distribution: -->
+      <zipfileset prefix="${app.short.name}-${file.release.label}/docs"
+                  dir="${docs.dist.dir}/docs"
+      />
+    </zip>
+  </target>
+
+
+  <!-- ============================================================== -->
+  <!-- CVS targets: -->
+  <!-- ============================================================== -->
+
+  <target name="build.cvs.ready" if="cvs.ready.label">
+    <cvs failonerror="yes" output="${out.dir}/cvs.txt" error="${out.dir}/cvs.error.txt"
+         command="-t rtag -r ${cvs.branch.label} -F -a ${cvs.ready.label} ${app.short.name}" />
+  </target>
+
+  <target name="build.cvs.unready" if="cvs.ready.label">
+    <cvs failonerror="yes" output="${out.dir}/cvs.txt" error="${out.dir}/cvs.error.txt"
+         command="-t rtag -r ${cvs.branch.label} -d -a ${cvs.ready.label} ${app.short.name}" />
+  </target>
+
+</project>
+<!-- ========= END OF FILE ========================================== -->
+
diff --git a/core/data/MANIFEST.MF b/core/data/MANIFEST.MF
new file mode 100644
index 0000000..72deeac
--- /dev/null
+++ b/core/data/MANIFEST.MF
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Created-By: @JDK_ID@
+Specification-Title: @APP_NAME@
+Specification-Vendor: (C) Vladimir Roubtsov
+Implementation-Vendor: @BUILD_USER_NAME@
+Specification-Version: @APP_MAJOR_VERSION@.@APP_MINOR_VERSION@
+Implementation-Version: @APP_MAJOR_VERSION@.@APP_MINOR_VERSION@.@APP_BUILD_ID@@APP_BUILD_RELEASE_TAG@ (@APP_BUILD_DATE@)
diff --git a/core/data/com/vladium/app/IAppVersion.java b/core/data/com/vladium/app/IAppVersion.java
new file mode 100644
index 0000000..07966f3
--- /dev/null
+++ b/core/data/com/vladium/app/IAppVersion.java
@@ -0,0 +1,41 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IAppVersion.java,v 1.1.1.1 2004/05/09 16:57:28 vlad_r Exp $
+ */
+package com.vladium.app;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IAppVersion
+{
+    // public: ................................................................
+
+
+    // filled in by the build:
+    
+    int APP_MAJOR_VERSION = /* @APP_MAJOR_VERSION@ */ 0;
+    int APP_MINOR_VERSION = /* @APP_MINOR_VERSION@ */ 0;
+
+    int APP_BUILD_ID = /* @APP_BUILD_ID@ */ 0;
+    String APP_BUILD_RELEASE_TAG = /* @APP_BUILD_RELEASE_TAG@ */ " (unsupported private build)";
+    String APP_BUILD_DATE = /* @APP_BUILD_DATE@ */ "unknown";
+
+    String APP_BUG_REPORT_LINK = /* @APP_BUG_REPORT_LINK@ */ "this private build is unsupported";
+    String APP_HOME_SITE_LINK = /* @APP_HOME_SITE_LINK@ */ "this private build is unsupported";
+
+    // derived properties [must be compile-time consts]:
+
+    String APP_BUILD_ID_AND_TAG = "" + APP_BUILD_ID + APP_BUILD_RELEASE_TAG;
+    
+    String APP_VERSION = "" + APP_MAJOR_VERSION + "." + APP_MINOR_VERSION;
+    String APP_VERSION_WITH_BUILD_ID_AND_TAG = APP_VERSION + "." + APP_BUILD_ID_AND_TAG;
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/AppLoggers.java b/core/java12/com/vladium/emma/AppLoggers.java
new file mode 100644
index 0000000..21437ed
--- /dev/null
+++ b/core/java12/com/vladium/emma/AppLoggers.java
@@ -0,0 +1,90 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AppLoggers.java,v 1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import com.vladium.logging.ILogLevels;
+import com.vladium.logging.Logger;
+import com.vladium.util.IProperties;
+import com.vladium.util.Strings;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+abstract class AppLoggers
+{
+    // public: ................................................................
+    
+    public static final String PREFIX_VERBOSITY                 = "verbosity.";
+
+    public static final String PROPERTY_VERBOSITY_LEVEL         = PREFIX_VERBOSITY + "level";
+    public static final String DEFAULT_VERBOSITY_LEVEL          = ILogLevels.INFO_STRING;
+    
+    public static final String PROPERTY_VERBOSITY_FILTER        = PREFIX_VERBOSITY + "filter";
+    
+    public static Logger create (final String appName, final IProperties properties, final Logger base)
+    {
+        if (properties == null)
+            throw new IllegalArgumentException ("null input: properties");
+        
+        // verbosity level:
+        
+        final int level;
+        {
+            final String _level = properties.getProperty (PROPERTY_VERBOSITY_LEVEL,
+                                                          DEFAULT_VERBOSITY_LEVEL);
+            level = Logger.stringToLevel (_level);
+        }
+        
+        // verbosity filter:
+        
+        final Set filter;
+        {
+            final String _filter = properties.getProperty (PROPERTY_VERBOSITY_FILTER);
+            Set temp = null;
+            
+            if (_filter != null)
+            {
+                final StringTokenizer tokenizer = new StringTokenizer (_filter, COMMA_DELIMITERS);
+                if (tokenizer.countTokens () > 0)
+                {
+                    temp = new HashSet (tokenizer.countTokens ());
+                    while (tokenizer.hasMoreTokens ())
+                    {
+                        temp.add (tokenizer.nextToken ());
+                    }
+                }
+            }
+            
+            filter = temp;
+        }
+        
+        return Logger.create (level, null, appName, filter, base);
+    }
+    
+    
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+
+    private AppLoggers () {} // this class is not extendible
+    
+    private static final String COMMA_DELIMITERS    = "," + Strings.WHITE_SPACE;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/Command.java b/core/java12/com/vladium/emma/Command.java
new file mode 100644
index 0000000..c453605
--- /dev/null
+++ b/core/java12/com/vladium/emma/Command.java
@@ -0,0 +1,326 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Command.java,v 1.1.1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Properties;
+
+import com.vladium.logging.ILogLevels;
+import com.vladium.util.IConstants;
+import com.vladium.util.Property;
+import com.vladium.util.Strings;
+import com.vladium.util.XProperties;
+import com.vladium.util.args.IOptsParser;
+import com.vladium.emma.data.mergeCommand;
+import com.vladium.emma.instr.instrCommand;
+import com.vladium.emma.report.reportCommand;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Command
+{
+    // public: ................................................................
+    
+    
+    public static Command create (final String name, final String usageName, final String [] args)
+    {
+        final Command tool;
+        
+        // TODO: dynamic load here?
+        
+        if ("run".equals (name))
+            tool = new runCommand (usageName, args);
+        else if ("instr".equals (name))
+            tool = new instrCommand (usageName, args);
+        else if ("report".equals (name))
+            tool = new reportCommand (usageName, args);
+        else if ("merge".equals (name))
+            tool = new mergeCommand (usageName, args);
+        else
+            throw new IllegalArgumentException ("unknown command: [" + name + "]");    
+            
+        tool.initialize ();
+        
+        return tool;
+    }
+    
+    public abstract void run ();
+    
+    // protected: .............................................................
+
+    
+    protected Command (final String usageToolName, final String [] args)
+    {
+        m_usageToolName = usageToolName;
+        m_args = args != null ? (String []) args.clone () : IConstants.EMPTY_STRING_ARRAY;  
+    }
+    
+    protected abstract String usageArgsMsg ();
+
+    // TODO: is this useful (separate from <init>)?
+    protected void initialize ()
+    {
+        m_exit = false;
+        
+        if (m_out != null) try { m_out.flush (); } catch (Throwable ignore) {}
+        m_out = new PrintWriter (System.out, true);
+    }   
+    
+    protected final String getToolName ()
+    {
+        // TODO: embed build number etc
+        final String clsName = getClass ().getName ();
+        
+        return clsName.substring (0, clsName.length () - 7);
+    }
+    
+    protected final IOptsParser getOptParser (final ClassLoader loader)
+    {
+        return IOptsParser.Factory.create (usageResName (getToolName ()), loader,
+            usageMsgPrefix (m_usageToolName), USAGE_OPT_NAMES);
+    } 
+    
+    protected final boolean processOpt (final IOptsParser.IOpt opt)
+    {
+        final String on = opt.getCanonicalName ();
+        
+        if ("exit".equals (on)) // 'exit' should always be first in this else-if chain
+        {
+            m_exit = getOptionalBooleanOptValue (opt);
+            return true;
+        }
+        else if ("p".equals (on))
+        {
+            m_propertyFile = new File (opt.getFirstValue ());
+            return true;
+        }
+        else if ("verbose".equals (on))
+        {
+            setPropertyOverride (AppLoggers.PROPERTY_VERBOSITY_LEVEL, ILogLevels.VERBOSE_STRING);
+            return true;
+        }
+        else if ("quiet".equals (on))
+        {
+            setPropertyOverride (AppLoggers.PROPERTY_VERBOSITY_LEVEL, ILogLevels.WARNING_STRING);
+            return true;
+        }
+        else if ("silent".equals (on))
+        {
+            setPropertyOverride (AppLoggers.PROPERTY_VERBOSITY_LEVEL, ILogLevels.SEVERE_STRING);
+            return true;
+        }
+        else if ("debug".equals (on))
+        {
+            if (opt.getValueCount () == 0)
+                setPropertyOverride (AppLoggers.PROPERTY_VERBOSITY_LEVEL, ILogLevels.TRACE1_STRING);
+            else
+                setPropertyOverride (AppLoggers.PROPERTY_VERBOSITY_LEVEL, opt.getFirstValue ());
+            
+            return true;
+        }
+        else if ("debugcls".equals (on))
+        {
+            setPropertyOverride (AppLoggers.PROPERTY_VERBOSITY_FILTER, Strings.toListForm (Strings.merge (opt.getValues (), COMMA_DELIMITERS, true), ',')); 
+            return true;
+        }
+        
+        return false;
+    }
+    
+    protected final void processCmdPropertyOverrides (final IOptsParser.IOpts parsedopts)
+    {
+        final IOptsParser.IOpt [] popts = parsedopts.getOpts (EMMAProperties.GENERIC_PROPERTY_OVERRIDE_PREFIX);
+        if ((popts != null) && (popts.length != 0))
+        {
+            final Properties cmdOverrides = new XProperties ();
+            
+            for (int o = 0; o < popts.length; ++ o)
+            {
+                final IOptsParser.IOpt opt = popts [o];
+                final String on = opt.getName ().substring (opt.getPatternPrefix ().length ());
+                
+                // TODO: support mergeable prefixed opts?
+                
+                cmdOverrides.setProperty (on, opt.getFirstValue ());
+            }
+            
+            // command line user overrides are have highest precedence:
+            m_propertyOverrides = Property.combine (cmdOverrides, m_propertyOverrides);
+        }
+    }
+    
+    protected final boolean processFilePropertyOverrides ()
+    {
+        if (m_propertyFile != null)
+        {
+            final Properties fileOverrides;
+            
+            try
+            {
+                fileOverrides = Property.getPropertiesFromFile (m_propertyFile);
+            }
+            catch (IOException ioe)
+            {
+                exit (true, "property override file [" + m_propertyFile.getAbsolutePath () + "] could not be read", ioe, RC_USAGE);
+                return false;
+            }
+            
+            // props file overrides have second highest precendence:
+            m_propertyOverrides = Property.combine (m_propertyOverrides, fileOverrides); 
+        }
+        
+        return true;
+    }
+    
+    protected final void usageexit (final IOptsParser parser, final int level, final String msg)
+    {
+        if (msg != null)
+        {
+            m_out.print (usageMsgPrefix (m_usageToolName));
+            m_out.println (msg);
+        }
+        
+        if (parser != null)
+        {
+            m_out.println ();
+            m_out.print (usageMsgPrefix (m_usageToolName));
+            m_out.println (toolNameToCommandName (m_usageToolName) + " " + usageArgsMsg () + ",");
+            m_out.println ("  where options include:");
+            m_out.println ();
+            parser.usage (m_out, level, STDOUT_WIDTH);            
+        }
+        
+        m_out.println ();
+        exit (true, null, null, RC_USAGE);
+    }
+    
+    protected final void exit (final boolean showBuildID, final String msg, final Throwable t, final int rc)
+        throws EMMARuntimeException
+    {
+        if (showBuildID)
+        {
+            m_out.println (IAppConstants.APP_USAGE_BUILD_ID);
+        }
+        
+        if (msg != null)
+        {
+            m_out.print (toolNameToCommandName (m_usageToolName) + ": "); m_out.println (msg);
+        }
+
+        if (rc != RC_OK)
+        {
+            // error exit:
+            
+            //if ((showBuildID) || (msg != null)) m_out.println ();
+            
+            if (m_exit)
+            {
+                if (t != null) t.printStackTrace (m_out);
+                System.exit (rc);
+            }
+            else
+            {
+                if (t instanceof EMMARuntimeException)
+                    throw (EMMARuntimeException) t;
+                else if (t != null)
+                    throw msg != null ? new EMMARuntimeException (msg, t) : new EMMARuntimeException ("unexpected failure: ", t);
+            }
+        }
+        else
+        {
+            // normal exit: 't' is ignored
+            
+            if (m_exit)
+            {
+                System.exit (0);
+            }
+        }
+    }
+
+    protected static boolean getOptionalBooleanOptValue (final IOptsParser.IOpt opt)
+    {
+        if (opt.getValueCount () == 0)
+            return true;
+        else
+        {
+            final String v = opt.getFirstValue ().toLowerCase ();
+         
+            return Property.toBoolean (v);
+        }
+    }
+    
+    protected static String [] getListOptValue (final IOptsParser.IOpt opt, final String delimiters, final boolean processAtFiles)
+        throws IOException
+    {
+        return Strings.mergeAT (opt.getValues (), delimiters, processAtFiles);
+    }
+    
+    protected static String usageMsgPrefix (final String toolName)
+    {
+        return toolNameToCommandName (toolName).concat (" usage: ");
+    }
+    
+    protected static String usageResName (final String toolName)
+    {
+        return toolName.replace ('.', '/').concat ("_usage.res");
+    }
+    
+    protected static String toolNameToCommandName (final String toolName)
+    {
+        final int lastDot = toolName.lastIndexOf ('.');
+        
+        return lastDot > 0 ? toolName.substring (lastDot + 1) : toolName;
+    }
+    
+
+    protected final String m_usageToolName;
+    protected final String [] m_args;
+    
+    protected File m_propertyFile;
+    protected Properties m_propertyOverrides;
+    protected boolean m_exit;
+    protected PrintWriter m_out; // this is set independently from Logger by design
+    
+    protected static final String COMMA_DELIMITERS    = "," + Strings.WHITE_SPACE;
+    protected static final String PATH_DELIMITERS     = ",".concat (File.pathSeparator);
+
+    protected static final String [] USAGE_OPT_NAMES = new String [] {"h", "help"};
+    protected static final int STDOUT_WIDTH = 80;    
+    
+    // return codes used with System.exit():
+    protected static final int RC_OK          = 0;
+    protected static final int RC_USAGE       = 1;
+    protected static final int RC_UNEXPECTED  = 2;
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+
+    /*
+     * Lazily instantiates m_propertyOverrides if necessary.
+     */
+    private void setPropertyOverride (final String key, final String value)
+    {
+        Properties propertyOverrides = m_propertyOverrides;
+        if (propertyOverrides == null)
+        {
+            m_propertyOverrides = propertyOverrides = new XProperties ();
+        }
+        
+        propertyOverrides.setProperty (key, value);
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/EMMAException.java b/core/java12/com/vladium/emma/EMMAException.java
new file mode 100644
index 0000000..9a42f9e
--- /dev/null
+++ b/core/java12/com/vladium/emma/EMMAException.java
@@ -0,0 +1,94 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: EMMAException.java,v 1.1.1.1 2004/05/09 16:57:29 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import com.vladium.util.exception.AbstractException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class EMMAException extends AbstractException
+{
+    // public: ................................................................
+
+    /**
+     * Constructs an exception with null message and null cause.
+     */    
+    public EMMAException ()
+    {
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *
+     * @param message the detail message [can be null]
+     */
+    public EMMAException (final String message)
+    {
+        super (message);
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *   
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     *
+     * @see java.text.MessageFormat
+     */
+    public EMMAException (final String message, final Object [] arguments)
+    {
+        super (message, arguments);
+    }
+    
+    /**
+     * Constructs an exception with null error message/code and given cause.
+     *
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public EMMAException (final Throwable cause)
+    {
+        super (cause);
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public EMMAException (final String message, final Throwable cause)
+    {
+        super (message, cause);
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     * @param cause the cause [nested exception] [can be null]
+     *
+     * @see java.text.MessageFormat
+     */
+    public EMMAException (final String message, final Object [] arguments, final Throwable cause)
+    {
+        super (message, arguments, cause);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/EMMAProperties.java b/core/java12/com/vladium/emma/EMMAProperties.java
new file mode 100644
index 0000000..14bfba4
--- /dev/null
+++ b/core/java12/com/vladium/emma/EMMAProperties.java
@@ -0,0 +1,202 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: EMMAProperties.java,v 1.1.1.1.2.3 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.WeakHashMap;
+
+import com.vladium.util.ClassLoaderResolver;
+import com.vladium.util.IProperties;
+import com.vladium.util.Property;
+import com.vladium.emma.report.IReportProperties;
+import com.vladium.emma.report.ReportProperties;
+
+// ----------------------------------------------------------------------------
+/**
+ * A reflection of "${IAppConstants.APP_PROPERTY_RES_NAME}.properties" resource
+ * as viewed by a given classloader.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class EMMAProperties
+{
+    // public: ................................................................
+    
+    public static final String GENERIC_PROPERTY_OVERRIDE_PREFIX = "D";
+
+    // [the DEFAULT_xxx settings duplicate the defaults in APP_DEFAULT_PROPERTIES_RES_NAME
+    // resource to provide a safe fallback option if that resource cannot be loaded]
+    
+    public static final String DEFAULT_META_DATA_OUT_FILE       = "coverage.em";
+    public static final Boolean DEFAULT_META_DATA_OUT_MERGE     = Boolean.TRUE;
+    public static final String PREFIX_META_DATA                 = "metadata.";
+    public static final String PROPERTY_META_DATA_OUT_FILE      = PREFIX_META_DATA + "out.file";
+    public static final String PROPERTY_META_DATA_OUT_MERGE     = PREFIX_META_DATA + "out.merge";
+    
+    public static final String DEFAULT_COVERAGE_DATA_OUT_FILE   = "coverage.ec";
+    public static final Boolean DEFAULT_COVERAGE_DATA_OUT_MERGE = Boolean.TRUE;
+    public static final String PREFIX_COVERAGE_DATA             = "coverage.";
+    public static final String PROPERTY_COVERAGE_DATA_OUT_FILE  = PREFIX_COVERAGE_DATA + "out.file";
+    public static final String PROPERTY_COVERAGE_DATA_OUT_MERGE = PREFIX_COVERAGE_DATA + "out.merge";
+
+    public static final String DEFAULT_SESSION_DATA_OUT_FILE    = "coverage.es";
+    public static final Boolean DEFAULT_SESSION_DATA_OUT_MERGE  = Boolean.TRUE;
+    public static final String PREFIX_SESSION_DATA              = "session.";
+    public static final String PROPERTY_SESSION_DATA_OUT_FILE   = PREFIX_SESSION_DATA + "out.file";
+    public static final String PROPERTY_SESSION_DATA_OUT_MERGE  = PREFIX_SESSION_DATA + "out.merge";
+    
+    public static final String PROPERTY_TEMP_FILE_EXT           = ".et";
+    
+    public static final Map SYSTEM_PROPERTY_REDIRECTS; // set in <clinit>
+    
+    
+    /**
+     * Global method used to create an appearance that all app work has been
+     * done at the same point in time (useful for setting archive and report
+     * timestamps etc).
+     * 
+     * @return the result of System.currentTimeMillis (), evaluated on the
+     * first call only
+     */
+    public static synchronized long getTimeStamp ()
+    {
+        long result = s_timestamp;
+        if (result == 0)
+        {
+            s_timestamp = result = System.currentTimeMillis ();
+        }
+
+        return result; 
+    }
+    
+    
+    public static String makeAppVersion (final int major, final int minor, final int build)
+    {
+        final StringBuffer buf = new StringBuffer ();
+        
+        buf.append (major);
+        buf.append ('.');
+        buf.append (minor);
+        buf.append ('.');
+        buf.append (build);
+        
+        return buf.toString ();
+    }
+    
+    
+    /**
+     * Wraps a Properties into a IProperties with the app's standard property
+     * mapping in place.
+     * 
+     * @param properties [null results in null result]
+     */
+    public static IProperties wrap (final Properties properties)
+    {
+        if (properties == null) return null;
+        
+        return IProperties.Factory.wrap (properties, ReportProperties.REPORT_PROPERTY_MAPPER);
+    }
+   
+    /**
+     * Retrieves application properties as classloader resource with a given name.
+     * [as seen from ClassLoaderResolver.getClassLoader ()]. The result is cached
+     * using this loader as a weak key.
+     * 
+     * @return properties [can be null]
+     */
+    public static synchronized IProperties getAppProperties ()
+    {
+        final ClassLoader loader = ClassLoaderResolver.getClassLoader ();
+        
+        return getAppProperties (loader);
+    }
+
+    public static synchronized IProperties getAppProperties (final ClassLoader loader)
+    {
+        IProperties properties = (IProperties) s_properties.get (loader);
+
+        if (properties != null)
+            return properties;
+        else
+        {
+            final String appName = IAppConstants.APP_NAME_LC;
+            
+            // note: this does not use Property.getAppProperties() by design,
+            // because that mechanism is not property alias-capable
+            
+            final IProperties systemRedirects = wrap (Property.getSystemPropertyRedirects (EMMAProperties.SYSTEM_PROPERTY_REDIRECTS));
+            final IProperties appDefaults = wrap (Property.getProperties (appName + "_default.properties", loader));
+            final IProperties systemFile;
+            {
+                final String fileName = Property.getSystemProperty (appName + ".properties");
+                final File file = fileName != null
+                    ? new File (fileName)
+                    : null;
+
+                systemFile = wrap (Property.getLazyPropertiesFromFile (file));
+            }
+            final IProperties system = wrap (Property.getSystemProperties (appName));
+            final IProperties userOverrides = wrap (Property.getProperties (appName + ".properties", loader));
+            
+            // "vertical" inheritance order:
+            //      (1) user overrides ("emma.properties" classloader resource)
+            //      (2) system properties (java.lang.System.getProperties(),
+            //                             filtered by the app prefix)
+            //      (3) system file properties ("emma.properties" system property,
+            //                                  interpreted as a property file)
+            //      (4) app defaults ("emma_default.properties" classloader resource)
+            //      (5) system property redirects (report.out.encoding->file.encoding,
+            //                                     report.out.dir->user.dir, etc)
+        
+            properties = IProperties.Factory.combine (userOverrides,
+                         IProperties.Factory.combine (system,
+                         IProperties.Factory.combine (systemFile,
+                         IProperties.Factory.combine (appDefaults,
+                                                      systemRedirects))));
+
+            s_properties.put (loader, properties);
+            
+            return properties;
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private EMMAProperties () {} // prevent subclassing
+        
+    
+    private static long s_timestamp;
+    
+    private static final Map /* ClassLoader->Properties */ s_properties; // set in <clinit>
+    
+    static
+    {
+        s_properties = new WeakHashMap ();
+        
+        final Map redirects = new HashMap ();
+        redirects.put (IReportProperties.PREFIX.concat (IReportProperties.OUT_ENCODING),
+                       "file.encoding");
+        redirects.put (IReportProperties.PREFIX.concat (IReportProperties.OUT_DIR),
+                       "user.dir");
+                       
+        SYSTEM_PROPERTY_REDIRECTS = Collections.unmodifiableMap (redirects);
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/EMMARuntimeException.java b/core/java12/com/vladium/emma/EMMARuntimeException.java
new file mode 100644
index 0000000..2f7883d
--- /dev/null
+++ b/core/java12/com/vladium/emma/EMMARuntimeException.java
@@ -0,0 +1,94 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: EMMARuntimeException.java,v 1.1.1.1 2004/05/09 16:57:29 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import com.vladium.util.exception.AbstractRuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class EMMARuntimeException extends AbstractRuntimeException
+{
+    // public: ................................................................
+
+    /**
+     * Constructs an exception with null message and null cause.
+     */    
+    public EMMARuntimeException ()
+    {
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *
+     * @param message the detail message [can be null]
+     */
+    public EMMARuntimeException (final String message)
+    {
+        super (message);
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *   
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     *
+     * @see java.text.MessageFormat
+     */
+    public EMMARuntimeException (final String message, final Object [] arguments)
+    {
+        super (message, arguments);
+    }
+    
+    /**
+     * Constructs an exception with null error message/code and given cause.
+     *
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public EMMARuntimeException (final Throwable cause)
+    {
+        super (cause);
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public EMMARuntimeException (final String message, final Throwable cause)
+    {
+        super (message, cause);
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     * @param cause the cause [nested exception] [can be null]
+     *
+     * @see java.text.MessageFormat
+     */
+    public EMMARuntimeException (final String message, final Object [] arguments, final Throwable cause)
+    {
+        super (message, arguments, cause);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/IAppConstants.java b/core/java12/com/vladium/emma/IAppConstants.java
new file mode 100644
index 0000000..d5feb6b
--- /dev/null
+++ b/core/java12/com/vladium/emma/IAppConstants.java
@@ -0,0 +1,37 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IAppConstants.java,v 1.1.1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import com.vladium.app.IAppVersion;
+import com.vladium.jcd.lib.Types;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IAppConstants extends IAppVersion
+{
+    // public: ................................................................
+    
+    String APP_NAME         = "EMMA";
+    String APP_NAME_LC      = "emma";
+    String APP_COPYRIGHT    = "(C) Vladimir Roubtsov";
+    String APP_THROWABLE_BUILD_ID   = "[" + APP_NAME + " v" + APP_VERSION_WITH_BUILD_ID_AND_TAG + "]";
+    String APP_USAGE_BUILD_ID   = "[" + APP_NAME + " v" + APP_VERSION + ", build " + APP_BUILD_ID_AND_TAG + "]";
+    String APP_VERBOSE_BUILD_ID = "[" + APP_NAME + " v" + APP_VERSION + ", build " + APP_BUILD_ID_AND_TAG + " (" + APP_BUILD_DATE + ")]";
+    
+    String APP_USAGE_PREFIX = APP_NAME + " usage: ";
+    
+    String APP_PACKAGE = Types.getClassPackageName (IAppConstants.class);
+    
+    long DATA_FORMAT_VERSION    = 0x20L;
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/IAppErrorCodes.java b/core/java12/com/vladium/emma/IAppErrorCodes.java
new file mode 100644
index 0000000..d07c9b8
--- /dev/null
+++ b/core/java12/com/vladium/emma/IAppErrorCodes.java
@@ -0,0 +1,72 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IAppErrorCodes.java,v 1.1.1.1 2004/05/09 16:57:29 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IAppErrorCodes
+{
+    // public: ................................................................
+    
+    /** {throwable.toString(), bug report link} */
+    String UNEXPECTED_FAILURE       = "UNEXPECTED_FAILURE";
+    
+    /** {parameter name, value} */
+    String INVALID_PARAMETER_VALUE  = "INVALID_PARAMETER_VALUE";
+    
+    /** {value} */
+    String INVALID_COLUMN_NAME      = "INVALID_COLUMN_NAME";
+    
+    /** {parameter name} */
+    String REQUIRED_PARAMETER_MISSING = "REQUIRED_PARAMETER_MISSING"; 
+    
+    /** {app name} */
+    String SECURITY_RESTRICTION                 = "SECURITY_RESTRICTION:";
+    
+    /** {app name, appclassname, app classloader name} */
+    String MAIN_CLASS_BAD_DELEGATION            = "MAIN_CLASS_BAD_DELEGATION";
+    
+    /** {classname} */
+    String MAIN_CLASS_NOT_FOUND                 = "MAIN_CLASS_NOT_FOUND";
+    
+    /** {classname, throwable.toString()} */
+    String MAIN_CLASS_LOAD_FAILURE              = "MAIN_CLASS_LOAD_FAILURE";
+    
+    /** {classname} */
+    String MAIN_METHOD_NOT_FOUND                = "MAIN_METHOD_NOT_FOUND";
+    
+    /** {classname, throwable.toString()} */
+    String MAIN_METHOD_FAILURE                  = "MAIN_METHOD_FAILURE";
+    
+    //TODO: /** ?? */
+    String REPORT_GEN_FAILURE                   = "REPORT_GEN_FAILURE";    
+    
+    /** [none] */
+    String REPORT_IO_FAILURE                    = "REPORT_IO_FAILURE";
+    
+    /** {classname} */
+    String CLASS_STAMP_MISMATCH                 = "CLASS_STAMP_MISMATCH";
+    
+    /** {dir} */
+    String OUT_MKDIR_FAILURE                    = "OUT_MKDIR_FAILURE";
+    
+    /** [none] */
+    String INSTR_IO_FAILURE                     = "INSTR_IO_FAILURE";
+    
+    /** {filename} */
+    String OUT_IO_FAILURE                       = "OUT_IO_FAILURE";
+    
+    /** [none] */
+    String ARGS_IO_FAILURE                      = "ARGS_IO_FAILURE";
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/Processor.java b/core/java12/com/vladium/emma/Processor.java
new file mode 100644
index 0000000..0701030
--- /dev/null
+++ b/core/java12/com/vladium/emma/Processor.java
@@ -0,0 +1,119 @@
+/* Copyright (C) 2004 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Processor.java,v 1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import java.util.Properties;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.IProperties;
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+abstract class Processor
+{
+    // public: ................................................................
+
+
+    public synchronized void run ()
+    {
+        validateState ();
+        
+        // load tool properties:
+        final IProperties toolProperties;
+        {
+            final IProperties appProperties = EMMAProperties.getAppProperties ();
+            
+            toolProperties = IProperties.Factory.combine (m_propertyOverrides, appProperties);
+        }
+        if ($assert.ENABLED) $assert.ASSERT (toolProperties != null, "toolProperties is null"); // can be empty, though
+
+        final Logger current = Logger.getLogger ();
+        final Logger log = AppLoggers.create (m_appName, toolProperties, current);
+        
+        if (log.atTRACE1 ())
+        {
+            log.trace1 ("run", "complete tool properties:");
+            toolProperties.list (log.getWriter ());
+        }
+        
+        try
+        {
+            Logger.push (log);
+            m_log = log;
+        
+            _run (toolProperties);
+        }
+        finally
+        {
+            if (m_log != null)
+            {
+                Logger.pop (m_log);
+                m_log = null;
+            }
+        }
+    }
+
+    
+    public synchronized final void setAppName (final String appName)
+    {
+        m_appName = appName;
+    }
+    
+    /**
+     * 
+     * @param overrides [may be null (unsets the previous overrides)]
+     */
+    public synchronized final void setPropertyOverrides (final Properties overrides)
+    {
+        m_propertyOverrides = EMMAProperties.wrap (overrides);
+    }
+    
+    /**
+     * 
+     * @param overrides [may be null (unsets the previous overrides)]
+     */
+    public synchronized final void setPropertyOverrides (final IProperties overrides)
+    {
+        m_propertyOverrides = overrides;
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected Processor ()
+    {
+        // not publicly instantiable
+    }
+
+    protected abstract void _run (IProperties toolProperties);
+
+    
+    protected void validateState ()
+    {
+        // no Processor state needs validation
+        
+        // [m_appName allowed to be null]
+        // [m_propertyOverrides allowed to be null]
+    }
+    
+    
+    protected String m_appName; // used as logging prefix, can be null
+    protected IProperties m_propertyOverrides; // user override; can be null/empty for run()
+    protected Logger m_log; // not null only within run()
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/ClassDescriptor.java b/core/java12/com/vladium/emma/data/ClassDescriptor.java
new file mode 100644
index 0000000..d2595b6
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/ClassDescriptor.java
@@ -0,0 +1,231 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassDescriptor.java,v 1.1.1.1 2004/05/09 16:57:30 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.Serializable;
+
+import com.vladium.util.IConstants;
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ClassDescriptor implements IConstants, Serializable
+{
+    // public: ................................................................
+    
+    
+    public ClassDescriptor (final String packageVMName, final String name, final long stamp,
+                            final String srcFileName,
+                            final MethodDescriptor [] methods)
+    {
+        if (packageVMName == null)
+            throw new IllegalArgumentException ("null input: packageVMName");
+        if (name == null)
+            throw new IllegalArgumentException ("null input: name");
+        if (methods == null)
+            throw new IllegalArgumentException ("null input: methods");
+        
+        if ($assert.ENABLED)
+        {
+            for (int m = 0; m < methods.length; ++ m)
+            {
+                $assert.ASSERT (methods [m] != null, "methods [" + m + "] = null (length = " + methods.length + ")");
+            }
+        }
+        
+        m_packageVMName = packageVMName;
+        m_name = name;
+        m_stamp = stamp;
+        m_srcFileName = srcFileName;
+        m_methods = methods; // TODO: defensive copy?
+        
+        boolean completeLineNumberInfo = true;
+        for (int m = 0; m < m_methods.length; ++ m)
+        {
+            final MethodDescriptor method = methods [m];
+            
+            if (((method.getStatus () & IMetadataConstants.METHOD_NO_BLOCK_DATA) == 0) && ! m_methods [m].hasLineNumberInfo ())
+            {
+                completeLineNumberInfo = false;
+                break;
+            }
+        }
+        
+        m_hasCompleteLineNumberInfo = completeLineNumberInfo;
+    }
+    
+    
+    // equality is defined based on <m_packageVMName, m_name> only (m_stamp not mixed in by design):
+    
+    public final boolean equals (final Object rhs)
+    {
+        if (! (rhs instanceof ClassDescriptor)) return false;
+        
+        final ClassDescriptor _rhs = (ClassDescriptor) rhs;
+        
+        if (hashCode () != _rhs.hashCode ()) return false;
+        
+        if (! m_name.equals (_rhs.m_name)) return false;
+        if (! m_packageVMName.equals (_rhs.m_packageVMName)) return false;
+        
+        return true;
+    }
+    
+    public final int hashCode ()
+    {
+        if (m_hash == 0)
+        {
+            final int hash = m_name.hashCode () + 16661 * m_packageVMName.hashCode ();
+            m_hash = hash;
+            
+            return hash;
+        }
+        
+        return m_hash;
+    }
+    
+    
+    public final String getPackageVMName ()
+    {
+        return m_packageVMName;
+    }
+    
+    public final String getName ()
+    {
+        return m_name;
+    }
+    
+    public final long getStamp ()
+    {
+        return m_stamp;
+    }
+    
+    public final String getClassVMName ()
+    {
+        // TODO: use Descriptors API?
+        if (m_packageVMName.length () == 0)
+            return m_name;
+        else
+            return new StringBuffer (m_packageVMName).append ("/").append (m_name).toString ();
+    }
+    
+    public final String getSrcFileName ()
+    {
+        return m_srcFileName;
+    }
+    
+    public final MethodDescriptor [] getMethods ()
+    {
+        return m_methods; // no defensive copy
+    }
+    
+    public final boolean hasSrcFileInfo ()
+    {
+        return m_srcFileName != null;
+    }
+    
+    public final boolean hasCompleteLineNumberInfo ()
+    {
+        return m_hasCompleteLineNumberInfo;
+    }
+    
+    
+    public String toString ()
+    {
+        return toString ("");
+    }
+    
+    public String toString (final String indent)
+    {
+        StringBuffer s = new StringBuffer (indent + "class [" + (m_packageVMName.length () > 0 ? m_packageVMName + "/" : "") + m_name + "] descriptor:");
+        
+        for (int m = 0; m < m_methods.length; ++ m)
+        {
+            s.append (EOL);
+            s.append (m_methods [m].toString (indent + INDENT_INCREMENT));
+        }
+        
+        return s.toString ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    static ClassDescriptor readExternal (final DataInput in)
+        throws IOException
+    {
+        final String packageVMName = in.readUTF ();
+        final String name = in.readUTF ();
+        
+        final long stamp = in.readLong ();
+        
+        final byte srcFileNameFlag = in.readByte ();
+        final String srcFileName = srcFileNameFlag != 0 ? in.readUTF () : null;
+        
+        final int length = in.readInt ();
+        final MethodDescriptor [] methods = new MethodDescriptor [length];
+        for (int i = 0; i < length; ++ i)
+        {
+            methods [i] = MethodDescriptor.readExternal (in);
+        }
+        
+        return new ClassDescriptor (packageVMName, name, stamp, srcFileName, methods);
+    }
+    
+    static void writeExternal (final ClassDescriptor cls, final DataOutput out)
+        throws IOException
+    {
+        out.writeUTF (cls.m_packageVMName);
+        out.writeUTF (cls.m_name);
+        
+        out.writeLong (cls.m_stamp);
+        
+        if (cls.m_srcFileName != null)
+        {
+            out.writeByte (1);
+            out.writeUTF (cls.m_srcFileName);
+        }
+        else
+        {
+            out.writeByte (0);
+        }
+        
+        final MethodDescriptor [] methods = cls.m_methods;
+        final int length = methods.length;
+        out.writeInt (length);
+        for (int i = 0; i < length; ++ i)
+        {
+            MethodDescriptor.writeExternal (methods [i], out);
+        }
+        
+        // [m_hash and m_hasCompleteLineNumberInfo are transient data]
+    }
+    
+    // private: ...............................................................
+    
+    
+    private final String m_packageVMName; // in JVM format, no trailing '/' [never null]
+    private final String m_name; // relative to (m_packageName + '/') [never null]
+    private final long m_stamp;
+    private final String m_srcFileName; // relative to (m_packageName + '/') [can be null]
+    private final MethodDescriptor [] m_methods; // [never null, could be empty]
+    
+    private final boolean m_hasCompleteLineNumberInfo;
+    private transient int m_hash;
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/data/CoverageData.java b/core/java12/com/vladium/emma/data/CoverageData.java
new file mode 100644
index 0000000..540756c
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/CoverageData.java
@@ -0,0 +1,230 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CoverageData.java,v 1.1.1.1 2004/05/09 16:57:31 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+final class CoverageData implements ICoverageData, Cloneable
+{
+    // public: ................................................................
+    
+    // TODO: duplicate issue
+       
+    public Object lock ()
+    {
+        return m_coverageMap;
+    }
+    
+    public ICoverageData shallowCopy ()
+    {
+        final CoverageData _clone;
+        try
+        {
+            _clone = (CoverageData) super.clone ();
+        }
+        catch (CloneNotSupportedException cnse)
+        {
+            throw new Error (cnse.toString ());
+        }
+        
+        final HashMap _coverageMap;
+        
+        synchronized (lock ())
+        {
+            _coverageMap = (HashMap) m_coverageMap.clone ();
+        }
+        
+        _clone.m_coverageMap = _coverageMap;
+        
+        return _clone;
+    }
+    
+    public int size ()
+    {
+        return m_coverageMap.size (); 
+    }
+
+    public DataHolder getCoverage (final ClassDescriptor cls)
+    {
+        if (cls == null) throw new IllegalArgumentException ("null input: cls");
+        
+        return (DataHolder) m_coverageMap.get (cls.getClassVMName ());
+    }
+    
+    public void addClass (final boolean [][] coverage, final String classVMName, final long stamp)
+    {
+        m_coverageMap.put (classVMName, new DataHolder (coverage, stamp));
+    }
+    
+    // IMergeable:
+    
+    public boolean isEmpty ()
+    {
+        return m_coverageMap.isEmpty ();
+    }
+
+    /*
+     * This method is not MT-safe wrt addClass() etc.
+     * 
+     * note: rhs entries override current entries if they have different stamps;
+     * otherwise, the data is merged 
+     */    
+    public IMergeable merge (final IMergeable rhs)
+    {
+        if ((rhs == null) || rhs.isEmpty () || (rhs == this))
+            return this;
+        else
+        {
+            final CoverageData rhscdata = (CoverageData) rhs; // TODO: redesign so that the cast is not necessary
+            final Map rhscoverageData = rhscdata.m_coverageMap;
+            
+            for (Iterator entries = rhscoverageData.entrySet ().iterator (); entries.hasNext (); )
+            {
+                final Map.Entry entry = (Map.Entry) entries.next ();
+                final String classVMName = (String) entry.getKey ();
+                
+                final DataHolder rhsdata = (DataHolder) entry.getValue ();
+                // [assertion: rhsdata != null]
+                
+                final DataHolder data = (DataHolder) m_coverageMap.get (classVMName);
+                
+                if (data == null)
+                    m_coverageMap.put (classVMName, rhsdata);
+                else
+                {
+                    if (rhsdata.m_stamp != data.m_stamp)
+                        m_coverageMap.put (classVMName, rhsdata);
+                    else // merge two runtime profiles
+                    {
+                        final boolean [][] rhscoverage = rhsdata.m_coverage;
+                        final boolean [][] coverage = data.m_coverage;
+                        
+                        // [assertion: both coverage and rhscoverage aren't null]
+                    
+                        if ($assert.ENABLED) $assert.ASSERT (coverage.length == rhscoverage.length, "coverage.length [" + coverage.length + "] != rhscoverage.length [" + rhscoverage.length + "]");
+                        for (int m = 0, mLimit = coverage.length; m < mLimit; ++ m)
+                        {
+                            final boolean [] rhsmcoverage = rhscoverage [m];
+                            final boolean [] mcoverage = coverage [m];
+                            
+                            if (mcoverage == null)
+                            {
+                                if ($assert.ENABLED) $assert.ASSERT (rhsmcoverage == null, "mcoverage == null but rhsmcoverage != null");
+                                
+                                // [nothing to merge]
+                            }
+                            else
+                            {
+                                if ($assert.ENABLED) $assert.ASSERT (rhsmcoverage != null, "mcoverage != null but rhsmcoverage == null");
+                                if ($assert.ENABLED) $assert.ASSERT (mcoverage.length == rhsmcoverage.length, "mcoverage.length [" + mcoverage.length + "] != rhsmcoverage.length [" + rhsmcoverage.length + "]");
+                                
+                                for (int b = 0, bLimit = mcoverage.length; b < bLimit; ++ b)
+                                {
+                                    if (rhsmcoverage [b]) mcoverage [b] = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+                
+            return this;
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    CoverageData ()
+    {
+        m_coverageMap = new HashMap ();
+    }
+    
+    
+    static CoverageData readExternal (final DataInput in)
+        throws IOException
+    {
+        final int size = in.readInt ();
+        final HashMap coverageMap = new HashMap (size);
+        
+        for (int i = 0; i < size; ++ i)
+        {
+            final String classVMName = in.readUTF ();
+            final long stamp = in.readLong ();
+            
+            final int length = in.readInt ();
+            final boolean [][] coverage = new boolean [length][];
+            for (int c = 0; c < length; ++ c) 
+            {
+                coverage [c] = DataFactory.readBooleanArray (in);
+            }
+            
+            coverageMap.put (classVMName, new DataHolder (coverage, stamp));
+        }
+        
+        return new CoverageData (coverageMap);
+    }
+    
+    static void writeExternal (final CoverageData cdata, final DataOutput out)
+        throws IOException
+    {
+        final Map coverageMap = cdata.m_coverageMap;
+        
+        final int size = coverageMap.size ();
+        out.writeInt (size);
+        
+        final Iterator entries = coverageMap.entrySet ().iterator ();
+        for (int i = 0; i < size; ++ i)
+        {
+            final Map.Entry entry = (Map.Entry) entries.next ();
+            
+            final String classVMName = (String) entry.getKey ();
+            final DataHolder data = (DataHolder) entry.getValue ();
+            
+            final boolean [][] coverage = data.m_coverage;
+            
+            out.writeUTF (classVMName);
+            out.writeLong (data.m_stamp);
+            
+            final int length = coverage.length;
+            out.writeInt (length);
+            for (int c = 0; c < length; ++ c)
+            {
+                DataFactory.writeBooleanArray (coverage [c], out);
+            }
+        }
+    }
+
+    // private: ...............................................................
+    
+    
+    private CoverageData (final HashMap coverageMap)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (coverageMap != null, "coverageMap is null");
+        m_coverageMap = coverageMap;
+    }
+    
+    
+    private /*final*/ HashMap /* String(classVMName) -> DataHolder */ m_coverageMap; // never null
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/CoverageOptions.java b/core/java12/com/vladium/emma/data/CoverageOptions.java
new file mode 100644
index 0000000..5a688c7
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/CoverageOptions.java
@@ -0,0 +1,83 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CoverageOptions.java,v 1.1.1.1.2.1 2004/06/27 22:58:26 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.Serializable;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class CoverageOptions implements Serializable
+{
+    // public: ................................................................
+    
+    public boolean excludeSyntheticMethods ()
+    {
+        return m_excludeSyntheticMethods;
+    }
+    
+    public boolean excludeBridgeMethods ()
+    {
+        return m_excludeBridgeMethods;
+    }
+    
+    public boolean doSUIDCompensation ()
+    {
+        return m_doSUIDCompensation;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    /*
+     * Package-private to be accessble by CoverageOptionsFactory
+     * (the factory is in a separate source file to avoid spurious
+     * classloading dependency via InnerClasses attr)
+     */
+    CoverageOptions (final boolean excludeSyntheticMethods,
+                     final boolean excludeBridgeMethods,
+                     final boolean doSUIDCompensation)
+    {
+        m_excludeSyntheticMethods = excludeSyntheticMethods;
+        m_excludeBridgeMethods = excludeBridgeMethods;
+        m_doSUIDCompensation = doSUIDCompensation;
+    }
+    
+    
+    static CoverageOptions readExternal (final DataInput in)
+        throws IOException
+    {
+        return new CoverageOptions (in.readBoolean (),
+                                    in.readBoolean (),
+                                    in.readBoolean ());
+    }
+    
+    static void writeExternal (final CoverageOptions options, final DataOutput out)
+        throws IOException
+    {
+        out.writeBoolean (options.m_excludeSyntheticMethods);
+        out.writeBoolean (options.m_excludeBridgeMethods);
+        out.writeBoolean (options.m_doSUIDCompensation);
+    }
+    
+    // private: ...............................................................
+
+    
+    private final boolean m_excludeSyntheticMethods;
+    private final boolean m_excludeBridgeMethods;
+    private final boolean m_doSUIDCompensation;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/CoverageOptionsFactory.java b/core/java12/com/vladium/emma/data/CoverageOptionsFactory.java
new file mode 100644
index 0000000..af548c3
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/CoverageOptionsFactory.java
@@ -0,0 +1,70 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CoverageOptionsFactory.java,v 1.1.2.1 2004/06/27 22:58:26 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.util.Properties;
+
+import com.vladium.emma.instr.InstrProcessor;
+import com.vladium.util.IProperties;
+import com.vladium.util.Property;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public
+abstract class CoverageOptionsFactory
+{
+    // public: ................................................................
+
+    public static CoverageOptions create (final Properties properties)
+    {
+        final boolean excludeSyntheticMethods =
+            Property.toBoolean (properties.getProperty (InstrProcessor.PROPERTY_EXCLUDE_SYNTHETIC_METHODS,
+                                                        InstrProcessor.DEFAULT_EXCLUDE_SYNTHETIC_METHODS));
+                                                                        
+        final boolean excludeBridgeMethods =
+            Property.toBoolean (properties.getProperty (InstrProcessor.PROPERTY_EXCLUDE_BRIDGE_METHODS,
+                                                        InstrProcessor.DEFAULT_EXCLUDE_BRIDGE_METHODS));
+                                                                                            
+        final boolean doSUIDCompensaton =
+            Property.toBoolean (properties.getProperty (InstrProcessor.PROPERTY_DO_SUID_COMPENSATION,
+                                                        InstrProcessor.DEFAULT_DO_SUID_COMPENSATION));
+        
+        return new CoverageOptions (excludeSyntheticMethods, excludeBridgeMethods, doSUIDCompensaton);
+    }
+    
+    public static CoverageOptions create (final IProperties properties)
+    {
+        final boolean excludeSyntheticMethods =
+            Property.toBoolean (properties.getProperty (InstrProcessor.PROPERTY_EXCLUDE_SYNTHETIC_METHODS,
+                                                        InstrProcessor.DEFAULT_EXCLUDE_SYNTHETIC_METHODS));
+        
+        final boolean excludeBridgeMethods =
+            Property.toBoolean (properties.getProperty (InstrProcessor.PROPERTY_EXCLUDE_BRIDGE_METHODS,
+                                                        InstrProcessor.DEFAULT_EXCLUDE_BRIDGE_METHODS));
+                                                                                         
+        final boolean doSUIDCompensaton =
+            Property.toBoolean (properties.getProperty (InstrProcessor.PROPERTY_DO_SUID_COMPENSATION,
+                                                        InstrProcessor.DEFAULT_DO_SUID_COMPENSATION));
+        
+        return new CoverageOptions (excludeSyntheticMethods, excludeBridgeMethods, doSUIDCompensaton);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+
+    private CoverageOptionsFactory () {} // this class is not extendible
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/DataFactory.java b/core/java12/com/vladium/emma/data/DataFactory.java
new file mode 100644
index 0000000..e4d493e
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/DataFactory.java
@@ -0,0 +1,813 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: DataFactory.java,v 1.1.1.1.2.3 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.net.URL;
+import java.net.URLConnection;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppConstants;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class DataFactory
+{
+    // public: ................................................................
+    
+    // TODO: file compaction
+    // TODO: file locking
+    
+    // TODO: what's the best place for these?
+    
+    public static final byte TYPE_METADATA          = 0x0; // must start with 0
+    public static final byte TYPE_COVERAGEDATA      = 0x1; // must be consistent with mergeload()
+    
+    
+    public static IMergeable [] load (final File file)
+        throws IOException
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        return mergeload (file);
+    }
+    
+    public static void persist (final IMetaData data, final File file, final boolean merge)
+        throws IOException
+    {
+        if (data == null) throw new IllegalArgumentException ("null input: data");
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        if (! merge && file.exists ())
+        {
+            if (! file.delete ())
+                throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
+        }
+        
+        persist (data, TYPE_METADATA, file);
+    }
+    
+    public static void persist (final ICoverageData data, final File file, final boolean merge)
+        throws IOException
+    {
+        if (data == null) throw new IllegalArgumentException ("null input: data");
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        if (! merge && file.exists ())
+        {
+            if (! file.delete ())
+                throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
+        }
+        
+        persist (data, TYPE_COVERAGEDATA, file);
+    } 
+    
+    public static void persist (final ISessionData data, final File file, final boolean merge)
+        throws IOException
+    {
+        if (data == null) throw new IllegalArgumentException ("null input: data");
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        if (! merge && file.exists ())
+        {
+            if (! file.delete ())
+                throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
+        }
+        
+        persist (data.getMetaData (), TYPE_METADATA, file); 
+        persist (data.getCoverageData (), TYPE_COVERAGEDATA, file);
+    }
+
+    
+    public static IMetaData newMetaData (final CoverageOptions options)
+    {
+        return new MetaData (options);
+    }
+    
+    public static ICoverageData newCoverageData ()
+    {
+        return new CoverageData (); 
+    }
+
+    public static IMetaData readMetaData (final URL url)
+        throws IOException, ClassNotFoundException
+    {
+        ObjectInputStream oin = null;
+        
+        try
+        {
+            oin = new ObjectInputStream (new BufferedInputStream (url.openStream (), 32 * 1024));
+            
+            return (IMetaData) oin.readObject ();
+        }
+        finally
+        {
+            if (oin != null) try { oin.close (); } catch (Exception ignore) {} 
+        }
+    }
+    
+    public static void writeMetaData (final IMetaData data, final OutputStream out)
+        throws IOException
+    {
+        ObjectOutputStream oout = new ObjectOutputStream (out);
+        oout.writeObject (data);
+    }
+    
+    public static void writeMetaData (final IMetaData data, final URL url)
+        throws IOException
+    {
+        final URLConnection connection = url.openConnection ();
+        connection.setDoOutput (true);
+        
+        OutputStream out = null;
+        try
+        {
+            out = connection.getOutputStream ();
+            
+            writeMetaData (data, out);
+            out.flush ();
+        }
+        finally
+        {
+            if (out != null) try { out.close (); } catch (Exception ignore) {} 
+        }
+    }
+    
+    public static ICoverageData readCoverageData (final URL url)
+        throws IOException, ClassNotFoundException
+    {
+        ObjectInputStream oin = null;
+        
+        try
+        {
+            oin = new ObjectInputStream (new BufferedInputStream (url.openStream (), 32 * 1024));
+            
+            return (ICoverageData) oin.readObject ();
+        }
+        finally
+        {
+            if (oin != null) try { oin.close (); } catch (Exception ignore) {} 
+        }
+    }
+    
+    public static void writeCoverageData (final ICoverageData data, final OutputStream out)
+        throws IOException
+    {
+        // TODO: prevent concurrent modification problems here
+        
+        ObjectOutputStream oout = new ObjectOutputStream (out);
+        oout.writeObject (data);
+    }
+    
+    public static int [] readIntArray (final DataInput in)
+        throws IOException
+    {
+        final int length = in.readInt ();
+        if (length == NULL_ARRAY_LENGTH)
+            return null;
+        else
+        {
+            final int [] result = new int [length];
+            
+            // read array in reverse order:
+            for (int i = length; -- i >= 0; )
+            {
+                result [i] = in.readInt ();
+            }
+            
+            return result;
+        }
+    }
+    
+    public static boolean [] readBooleanArray (final DataInput in)
+        throws IOException
+    {
+        final int length = in.readInt ();
+        if (length == NULL_ARRAY_LENGTH)
+            return null;
+        else
+        {
+            final boolean [] result = new boolean [length];
+            
+            // read array in reverse order:
+            for (int i = length; -- i >= 0; )
+            {
+                result [i] = in.readBoolean ();
+            }
+            
+            return result;
+        }
+    }
+    
+    public static void writeIntArray (final int [] array, final DataOutput out)
+        throws IOException
+    {
+        if (array == null)
+            out.writeInt (NULL_ARRAY_LENGTH);
+        else
+        {
+            final int length = array.length;
+            out.writeInt (length);
+            
+            // write array in reverse order:
+            for (int i = length; -- i >= 0; )
+            {
+                out.writeInt (array [i]);
+            }
+        }
+    }
+    
+    public static void writeBooleanArray (final boolean [] array, final DataOutput out)
+        throws IOException
+    {
+        if (array == null)
+            out.writeInt (NULL_ARRAY_LENGTH);
+        else
+        {
+            final int length = array.length;
+            out.writeInt (length);
+            
+            // write array in reverse order:
+            for (int i = length; -- i >= 0; )
+            {
+                out.writeBoolean (array [i]);
+            }
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+
+    private static final class UCFileInputStream extends FileInputStream
+    {
+        public void close ()
+        {
+        }
+        
+        UCFileInputStream (final FileDescriptor fd)
+        {
+            super (fd);
+            
+            if ($assert.ENABLED) $assert.ASSERT (fd.valid (), "UCFileInputStream.<init>: FD invalid");
+        }
+        
+    } // end of nested class
+
+    private static final class UCFileOutputStream extends FileOutputStream
+    {
+        public void close ()
+        {
+        }
+        
+        UCFileOutputStream (final FileDescriptor fd)
+        {
+            super (fd);
+            
+            if ($assert.ENABLED) $assert.ASSERT (fd.valid (), "UCFileOutputStream.<init>: FD invalid");
+        }
+        
+    } // end of nested class
+
+    
+    private static final class RandomAccessFileInputStream extends BufferedInputStream
+    {
+        public final int read () throws IOException
+        {
+            final int rc = super.read ();
+            if (rc >= 0) ++ m_count;
+            
+            return rc;
+        }
+
+        public final int read (final byte [] b, final int off, final int len)
+            throws IOException
+        {
+            final int rc = super.read (b, off, len);
+            if (rc >= 0) m_count += rc;
+            
+            return rc;
+        }
+
+        public final int read (final byte [] b) throws IOException
+        {
+            final int rc = super.read (b);
+            if (rc >= 0) m_count += rc;
+            
+            return rc;
+        }
+        
+        public void close ()
+        {
+        }        
+
+
+        RandomAccessFileInputStream (final RandomAccessFile raf, final int bufSize)
+            throws IOException
+        {
+            super (new UCFileInputStream (raf.getFD ()), bufSize);
+        }
+        
+        final long getCount ()
+        {
+            return m_count;
+        }
+        
+        private long m_count;
+
+    } // end of nested class
+    
+    private static final class RandomAccessFileOutputStream extends BufferedOutputStream
+    {
+        public final void write (final byte [] b, final int off, final int len) throws IOException
+        {
+            super.write (b, off, len);
+            m_count += len;
+        }
+
+        public final void write (final byte [] b) throws IOException
+        {
+            super.write (b);
+            m_count += b.length;
+        }
+
+        public final void write (final int b) throws IOException
+        {
+            super.write (b);
+            ++ m_count;
+        }
+        
+        public void close ()
+        {
+        }
+        
+        
+        RandomAccessFileOutputStream (final RandomAccessFile raf, final int bufSize)
+            throws IOException
+        {
+            super (new UCFileOutputStream (raf.getFD ()), bufSize);
+        }
+        
+        final long getCount ()
+        {
+            return m_count;
+        }
+        
+        private long m_count;
+
+    } // end of nested class
+    
+    
+    private DataFactory () {} // prevent subclassing   
+
+    /*
+     * input checked by the caller
+     */
+    private static IMergeable [] mergeload (final File file)
+        throws IOException
+    {
+        final Logger log = Logger.getLogger ();
+        final boolean trace1 = log.atTRACE1 ();
+        final boolean trace2 = log.atTRACE2 ();
+        final String method = "mergeload";
+        
+        long start = 0, end;
+        
+        if (trace1) start = System.currentTimeMillis ();
+        
+        final IMergeable [] result = new IMergeable [2];
+        
+        if (! file.exists ())
+        {
+            throw new IOException ("input file does not exist: [" + file.getAbsolutePath () +  "]");
+        }
+        else
+        {
+            RandomAccessFile raf = null;
+            try
+            {
+                raf = new RandomAccessFile (file, "r");
+                
+                // 'file' is a valid existing file, but it could still be of 0 length or otherwise corrupt:
+                final long length = raf.length ();
+                if (trace1) log.trace1 (method, "[" + file + "]: file length = " + length);
+                
+                if (length < FILE_HEADER_LENGTH)
+                {
+                    throw new IOException ("file [" + file.getAbsolutePath () + "] is corrupt or was not created by " + IAppConstants.APP_NAME);
+                }
+                else
+                {
+                    // TODO: data version checks parallel to persist()
+                    
+                    if (length > FILE_HEADER_LENGTH) // return {null, null} in case of equality
+                    {
+                        raf.seek (FILE_HEADER_LENGTH);
+                                
+                        // [assertion: file length > FILE_HEADER_LENGTH]
+                        
+                        // read entries until the first corrupt entry or the end of the file:
+                        
+                        long position = FILE_HEADER_LENGTH;
+                        long entryLength;
+                        
+                        long entrystart = 0;
+                        
+                        while (true)
+                        {
+                            if (trace2) log.trace2 (method, "[" + file + "]: position " + raf.getFilePointer ());
+                            if (position >= length) break;
+                            
+                            entryLength = raf.readLong ();
+                            
+                            if ((entryLength <= 0) || (position + entryLength + ENTRY_HEADER_LENGTH > length))
+                                break;
+                            else
+                            {
+                                final byte type = raf.readByte ();
+                                if ((type < 0) || (type >= result.length))
+                                    break;
+                                    
+                                if (trace2) log.trace2 (method, "[" + file + "]: found valid entry of size " + entryLength + " and type " + type);
+                                {
+                                    if (trace2) entrystart = System.currentTimeMillis ();
+                                    final IMergeable data = readEntry (raf, type, entryLength);
+                                    if (trace2) log.trace2 (method, "entry read in " + (System.currentTimeMillis () - entrystart) + " ms");                                    
+                                    
+                                    final IMergeable current = result [type];
+                                    
+                                    if (current == null)
+                                        result [type] = data;
+                                    else
+                                        result [type] = current.merge (data); // note: later entries overrides earlier entries
+                                }
+                                
+                                position += entryLength + ENTRY_HEADER_LENGTH;
+                                
+                                if ($assert.ENABLED) $assert.ASSERT (raf.getFD ().valid (), "FD invalid"); 
+                                raf.seek (position);
+                            }
+                        }                        
+                    }
+                }
+            }
+            finally
+            {
+                if (raf != null) try { raf.close (); } catch (Throwable ignore) {}
+                raf = null;
+            }
+        }
+        
+        if (trace1)
+        {
+            end = System.currentTimeMillis ();
+            
+            log.trace1 (method, "[" + file + "]: file processed in " + (end - start) + " ms"); 
+        }
+        
+        return result;
+    }
+
+
+    /*
+     * input checked by the caller
+     */
+    private static void persist (final IMergeable data, final byte type, final File file)
+        throws IOException
+    {
+        final Logger log = Logger.getLogger ();
+        final boolean trace1 = log.atTRACE1 ();
+        final boolean trace2 = log.atTRACE2 ();
+        final String method = "persist";
+        
+        long start = 0, end;
+        
+        if (trace1) start = System.currentTimeMillis ();
+        
+        // TODO: 1.4 adds some interesting RAF open mode options as well
+        // TODO: will this benefit from extra buffering?
+        
+        // TODO: data version checks
+
+        RandomAccessFile raf = null;
+        try
+        {
+            boolean overwrite = false;
+            boolean truncate = false;
+                        
+            if (file.exists ())
+            {
+                // 'file' exists:
+                
+                if (! file.isFile ()) throw new IOException ("can persist in normal files only: " + file.getAbsolutePath ());
+                
+                raf = new RandomAccessFile (file, "rw");
+                
+                // 'file' is a valid existing file, but it could still be of 0 length or otherwise corrupt:
+                final long length = raf.length ();
+                if (trace1) log.trace1 (method, "[" + file + "]: existing file length = " + length);
+                
+                
+                if (length < 4)
+                {
+                    overwrite = true;
+                    truncate = (length > 0);
+                }
+                else
+                {
+                    // [assertion: file length >= 4]
+                    
+                    // check header info before reading further:
+                    final int magic = raf.readInt ();
+                    if (magic != MAGIC)
+                        throw new IOException ("cannot overwrite [" + file.getAbsolutePath () + "]: not created by " + IAppConstants.APP_NAME);
+                    
+                    if (length < FILE_HEADER_LENGTH)
+                    {
+                        // it's our file, but the header is corrupt: overwrite
+                        overwrite = true;
+                        truncate = true;
+                    }  
+                    else
+                    {
+                        // [assertion: file length >= FILE_HEADER_LENGTH]
+                        
+//                        if (! append)
+//                        {
+//                            // overwrite any existing data:
+//                            
+//                            raf.seek (FILE_HEADER_LENGTH);
+//                            writeEntry (raf, FILE_HEADER_LENGTH, data, type);
+//                        }
+//                        else
+                        {
+                            // check data format version info:
+                            final long dataVersion = raf.readLong ();
+                            
+                            if (dataVersion != IAppConstants.DATA_FORMAT_VERSION)
+                            {
+                                // read app version info for the error message:
+                                
+                                int major = 0, minor = 0, build = 0;
+                                boolean gotAppVersion = false;
+                                try
+                                {
+                                    major = raf.readInt ();
+                                    minor = raf.readInt ();
+                                    build = raf.readInt ();
+                                    
+                                    gotAppVersion = true;
+                                }
+                                catch (Throwable ignore) {}
+                                
+                                // TODO: error code here?
+                                if (gotAppVersion)
+                                {
+                                    throw new IOException ("cannot merge new data into [" + file.getAbsolutePath () + "]: created by another " + IAppConstants.APP_NAME + " version [" + makeAppVersion (major, minor, build) + "]");
+                                }
+                                else
+                                {
+                                    throw new IOException ("cannot merge new data into [" + file.getAbsolutePath () + "]: created by another " + IAppConstants.APP_NAME + " version"); 
+                                }
+                            }
+                            else
+                            {
+                                // [assertion: file header is valid and data format version is consistent]
+                                
+                                raf.seek (FILE_HEADER_LENGTH);
+                                
+                                if (length == FILE_HEADER_LENGTH)
+                                {
+                                    // no previous data entries: append 'data'
+                                    
+                                    writeEntry (log, raf, FILE_HEADER_LENGTH, data, type);
+                                }
+                                else
+                                {
+                                    // [assertion: file length > FILE_HEADER_LENGTH]
+                                    
+                                    // write 'data' starting with the first corrupt entry or the end of the file:
+                                    
+                                    long position = FILE_HEADER_LENGTH;
+                                    long entryLength;
+                                    
+                                    while (true)
+                                    {
+                                        if (trace2) log.trace2 (method, "[" + file + "]: position " + raf.getFilePointer ());
+                                        if (position >= length) break;
+                                        
+                                        entryLength = raf.readLong ();
+                                        
+                                        if ((entryLength <= 0) || (position + entryLength + ENTRY_HEADER_LENGTH > length))
+                                            break;
+                                        else
+                                        {
+                                            if (trace2) log.trace2 (method, "[" + file + "]: found valid entry of size " + entryLength);
+                                            
+                                            position += entryLength + ENTRY_HEADER_LENGTH; 
+                                            raf.seek (position);
+                                        }
+                                    }
+                                    
+                                    if (trace2) log.trace2 (method, "[" + file + "]: adding entry at position " + position);
+                                    writeEntry (log, raf, position, data, type);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            else
+            {
+                // 'file' does not exist:
+                
+                if (trace1) log.trace1 (method, "[" + file + "]: creating a new file");
+                
+                final File parent = file.getParentFile ();
+                if (parent != null) parent.mkdirs ();
+                
+                raf = new RandomAccessFile (file, "rw");
+                
+                overwrite = true;
+            }
+            
+            
+            if (overwrite)
+            {
+                // persist starting from 0 offset:
+                
+                if ($assert.ENABLED) $assert.ASSERT (raf != null, "raf = null");
+                
+                if (truncate) raf.seek (0);
+                writeFileHeader (raf);
+                if ($assert.ENABLED) $assert.ASSERT (raf.getFilePointer () == FILE_HEADER_LENGTH, "invalid header length: " + raf.getFilePointer ());
+                
+                writeEntry (log, raf, FILE_HEADER_LENGTH, data, type);
+            }
+        }
+        finally
+        {
+            if (raf != null) try { raf.close (); } catch (Throwable ignore) {}
+            raf = null;
+        }
+        
+        if (trace1)
+        {
+            end = System.currentTimeMillis ();
+            
+            log.trace1 (method, "[" + file + "]: file processed in " + (end - start) + " ms"); 
+        }
+    }
+    
+    private static void writeFileHeader (final DataOutput out)
+        throws IOException
+    {
+        out.writeInt (MAGIC);
+        
+        out.writeLong (IAppConstants.DATA_FORMAT_VERSION);
+        
+        out.writeInt (IAppConstants.APP_MAJOR_VERSION);
+        out.writeInt (IAppConstants.APP_MINOR_VERSION);
+        out.writeInt (IAppConstants.APP_BUILD_ID);
+    }
+    
+    private static void writeEntryHeader (final DataOutput out, final byte type)
+        throws IOException
+    {
+        out.writeLong (UNKNOWN); // length placeholder
+        out.writeByte (type);
+    }
+    
+    private static void writeEntry (final Logger log, final RandomAccessFile raf, final long marker, final IMergeable data, final byte type)
+        throws IOException
+    {
+        // [unfinished] entry header:
+        writeEntryHeader (raf, type);
+        
+        // serialize 'data' starting with the current raf position:
+        RandomAccessFileOutputStream rafout = new RandomAccessFileOutputStream (raf, IO_BUF_SIZE); // note: no new file descriptors created here
+        {
+//            ObjectOutputStream oout = new ObjectOutputStream (rafout);
+//            
+//            oout.writeObject (data);
+//            oout.flush ();
+//            oout = null;
+            
+            DataOutputStream dout = new DataOutputStream (rafout);
+            switch (type)
+            {
+                case TYPE_METADATA: MetaData.writeExternal ((MetaData) data, dout);
+                    break;
+                    
+                default /* TYPE_COVERAGEDATA */: CoverageData.writeExternal ((CoverageData) data, dout);
+                    break;
+                    
+            } // end of switch
+            dout.flush ();
+            dout = null;
+            
+            // truncate:
+            raf.setLength (raf.getFilePointer ());
+        }
+
+        // transact this entry [finish the header]:
+        raf.seek (marker);
+        raf.writeLong (rafout.getCount ());
+        if (DO_FSYNC) raf.getFD ().sync ();
+        
+        if (log.atTRACE2 ()) log.trace2 ("writeEntry", "entry [" + data.getClass ().getName () + "] length: " + rafout.getCount ());
+    }
+    
+    private static IMergeable readEntry (final RandomAccessFile raf, final byte type, final long entryLength)
+        throws IOException
+    {
+        final Object data;
+        
+        RandomAccessFileInputStream rafin = new RandomAccessFileInputStream (raf, IO_BUF_SIZE); // note: no new file descriptors created here
+        {
+//           ObjectInputStream oin = new ObjectInputStream (rafin);
+//            
+//            try
+//            {
+//                data = oin.readObject ();
+//            }
+//            catch (ClassNotFoundException cnfe)
+//            {
+//                // TODO: EMMA exception here
+//                throw new IOException ("could not read data entry: " + cnfe.toString ());
+//            }
+
+            DataInputStream din = new DataInputStream (rafin);
+            switch (type)
+            {
+                case TYPE_METADATA: data = MetaData.readExternal (din);
+                    break;
+                    
+                default /* TYPE_COVERAGEDATA */: data = CoverageData.readExternal (din);
+                    break;
+                    
+            } // end of switch
+        }
+        
+        if ($assert.ENABLED) $assert.ASSERT (rafin.getCount () == entryLength, "entry length mismatch: " + rafin.getCount () + " != " + entryLength);
+        
+        return (IMergeable) data;
+    }
+    
+    
+    /*
+     * This is cloned from EMMAProperties by design, to eliminate a CONSTANT_Class_info
+     * dependency between this and EMMAProperties classes.
+     */
+    private static String makeAppVersion (final int major, final int minor, final int build)
+    {
+        final StringBuffer buf = new StringBuffer ();
+        
+        buf.append (major);
+        buf.append ('.');
+        buf.append (minor);
+        buf.append ('.');
+        buf.append (build);
+        
+        return buf.toString ();
+    }
+    
+
+    private static final int NULL_ARRAY_LENGTH = -1;
+    
+    private static final int MAGIC = 0x454D4D41; // "EMMA"
+    private static final long UNKNOWN = 0L;
+    private static final int FILE_HEADER_LENGTH = 4 + 8 + 3 * 4; // IMPORTANT: update on writeFileHeader() changes
+    private static final int ENTRY_HEADER_LENGTH = 8 + 1; // IMPORTANT: update on writeEntryHeader() changes
+    private static final boolean DO_FSYNC = true;
+    private static final int IO_BUF_SIZE = 32 * 1024;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/data/ICoverageData.java b/core/java12/com/vladium/emma/data/ICoverageData.java
new file mode 100644
index 0000000..c7476c0
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/ICoverageData.java
@@ -0,0 +1,49 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ICoverageData.java,v 1.1.1.1 2004/05/09 16:57:31 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface ICoverageData extends IMergeable
+{
+    // public: ................................................................
+    
+    final class DataHolder
+    {
+        public DataHolder (final boolean [][] coverage, final long stamp)
+        {
+            m_coverage = coverage;
+            m_stamp = stamp;
+        }
+        
+        public final boolean [][] m_coverage;
+        public final long m_stamp;
+        
+    } // end of nested class
+    
+    Object lock ();
+    
+    ICoverageData shallowCopy ();
+    
+    int size ();
+    
+    /**
+     * can return null
+     * can return data holder with a different version stamp than cls.getStamp()
+     */
+    DataHolder getCoverage (ClassDescriptor cls);
+    
+    //void setImmutable (); // TODO: this only disables addClass(), not coverage array updates; rename
+    void addClass (boolean [][] coverage, String classVMName, long stamp);
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/IMergeable.java b/core/java12/com/vladium/emma/data/IMergeable.java
new file mode 100644
index 0000000..b484ece
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/IMergeable.java
@@ -0,0 +1,30 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IMergeable.java,v 1.1.1.1 2004/05/09 16:57:31 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.Serializable;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IMergeable extends Serializable
+{
+    // public: ................................................................
+    
+    boolean isEmpty ();
+    
+    /**
+     * Caller must always switch to the returned handle. 
+     */
+    IMergeable merge (IMergeable data);
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/IMetaData.java b/core/java12/com/vladium/emma/data/IMetaData.java
new file mode 100644
index 0000000..14fe0ce
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/IMetaData.java
@@ -0,0 +1,41 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IMetaData.java,v 1.1.1.1 2004/05/09 16:57:31 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.util.Iterator;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IMetaData extends IMergeable
+{
+    // public: ................................................................
+    
+    Object lock ();
+    
+    IMetaData shallowCopy ();
+    
+    CoverageOptions getOptions ();
+    
+    int size ();
+    boolean hasSrcFileData ();
+    boolean hasLineNumberData ();
+    
+//    boolean hasDescriptor (ClassDescriptor cls);
+    boolean hasDescriptor (String classVMName);
+    
+    Iterator /* ClassDescripor */ iterator ();
+    
+    //void setImmutable (); // TODO: this only disables add(); rename
+    boolean add (ClassDescriptor cls, boolean overwrite);    
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/data/IMetadataConstants.java b/core/java12/com/vladium/emma/data/IMetadataConstants.java
new file mode 100644
index 0000000..8fb537d
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/IMetadataConstants.java
@@ -0,0 +1,30 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IMetadataConstants.java,v 1.1.1.1 2004/05/09 16:57:31 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, 2003
+ */
+public
+interface IMetadataConstants
+{
+    // public: ................................................................
+    
+    int METHOD_NO_LINE_NUMBER_TABLE = 0x01;
+    int METHOD_ABSTRACT_OR_NATIVE   = 0x02;
+    int METHOD_EXCLUDED             = 0x04;
+    int METHOD_ADDED                = 0x08;
+    
+    int METHOD_NO_BLOCK_DATA = (METHOD_ABSTRACT_OR_NATIVE | METHOD_EXCLUDED | METHOD_ADDED);
+    int METHOD_NO_LINE_DATA = (METHOD_NO_LINE_NUMBER_TABLE | METHOD_NO_BLOCK_DATA);
+    
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/ISessionData.java b/core/java12/com/vladium/emma/data/ISessionData.java
new file mode 100644
index 0000000..a216497
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/ISessionData.java
@@ -0,0 +1,24 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ISessionData.java,v 1.1.1.1 2004/05/09 16:57:32 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface ISessionData
+{
+    // public: ................................................................
+    
+    IMetaData getMetaData ();
+    ICoverageData getCoverageData ();
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/MergeProcessor.java b/core/java12/com/vladium/emma/data/MergeProcessor.java
new file mode 100644
index 0000000..dbae3d0
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/MergeProcessor.java
@@ -0,0 +1,368 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: MergeProcessor.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.Files;
+import com.vladium.util.IConstants;
+import com.vladium.util.IProperties;
+import com.vladium.util.asserts.$assert;
+import com.vladium.util.exception.Exceptions;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.Processor;
+
+// ----------------------------------------------------------------------------
+/*
+ * This class was not meant to be public by design. It is made to to work around
+ * access bugs in reflective invocations. 
+ */
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class MergeProcessor extends Processor
+                           implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    public static MergeProcessor create ()
+    {
+        return new MergeProcessor ();
+    }
+
+    /**
+     * 
+     * @param path [null is equivalent to an empty array]
+     */
+    public synchronized final void setDataPath (final String [] path)
+    {
+        if ((path == null) || (path.length == 0))
+            m_dataPath = IConstants.EMPTY_FILE_ARRAY;
+        else
+            m_dataPath = Files.pathToFiles (path, true);
+    }
+    
+    /**
+     * NOTE: there is no setter for merge attribute because this processor
+     * always overwrites the out file [to ensure compaction]
+     * 
+     * @param fileName [null unsets the previous override setting]
+     */
+    public synchronized final void setSessionOutFile (final String fileName)
+    {
+        if (fileName == null)
+            m_sdataOutFile = null;
+        else
+        {
+            final File _file = new File (fileName);
+                
+            if (_file.exists () && ! _file.isFile ())
+                throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]");
+                
+            m_sdataOutFile = _file;
+        }
+    }
+    
+    // protected: .............................................................
+
+    
+    protected void validateState ()
+    {
+        super.validateState ();
+        
+        if (m_dataPath == null)
+            throw new IllegalStateException ("data path not set");
+        
+        // [m_sdataOutFile can be null]
+        
+        // [m_propertyOverrides can be null]
+    }
+    
+    
+    protected void _run (final IProperties toolProperties)
+    {
+        final Logger log = m_log;
+
+        final boolean verbose = m_log.atVERBOSE ();
+        if (verbose)
+        {
+            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
+            
+            // [assertion: m_dataPath != null]
+            log.verbose ("input data path:");
+            log.verbose ("{");
+            for (int p = 0; p < m_dataPath.length; ++ p)
+            {
+                final File f = m_dataPath [p];
+                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
+                
+                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
+            }
+            log.verbose ("}");
+        }
+        else
+        {
+            log.info ("processing input files ...");
+        }
+        
+        // get the data out settings:
+        File sdataOutFile = m_sdataOutFile;
+        {
+            if (sdataOutFile == null)
+                sdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE,
+                                                                     EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE));
+        }
+                
+        RuntimeException failure = null;
+        try
+        {
+            IMetaData mdata = null;
+            ICoverageData cdata = null;
+            
+            // merge all data files:
+            try
+            {
+                final long start = log.atINFO () ? System.currentTimeMillis () : 0;
+                
+                for (int f = 0; f < m_dataPath.length; ++ f)
+                {
+                    final File dataFile = m_dataPath [f];
+                    if (verbose) log.verbose ("processing input file [" + dataFile.getAbsolutePath () + "] ...");
+                    
+                    final IMergeable [] fileData = DataFactory.load (dataFile);
+                    
+                    final IMetaData _mdata = (IMetaData) fileData [DataFactory.TYPE_METADATA];
+                    if (_mdata != null)
+                    {
+                        if (verbose) log.verbose ("  loaded " + _mdata.size () + " metadata entries");
+                        
+                        if (mdata == null)
+                            mdata = _mdata;
+                        else
+                            mdata = (IMetaData) mdata.merge (_mdata); // note: later datapath entries override earlier ones
+                    }
+                    
+                    final ICoverageData _cdata = (ICoverageData) fileData [DataFactory.TYPE_COVERAGEDATA];
+                    if (_cdata != null)
+                    {
+                        if (verbose) log.verbose ("  loaded " + _cdata.size () + " coverage data entries");
+                        
+                        if (cdata == null)
+                            cdata = _cdata;
+                        else
+                            cdata = (ICoverageData) cdata.merge (_cdata); // note: later datapath entries override earlier ones
+                    }
+                    
+                    ++ m_dataFileCount;
+                }
+                
+                if (log.atINFO ())
+                {
+                    final long end = System.currentTimeMillis ();
+                    
+                    log.info (m_dataFileCount + " file(s) read and merged in " + (end - start) + " ms");
+                }
+                
+                if (((mdata == null) || mdata.isEmpty ()) && ((cdata == null) || cdata.isEmpty ()))
+                {
+                    log.warning ("nothing to do: no metadata or coverage data found in any of the input files");
+                    
+                    // TODO: throw exception or exit quietly?
+                    return;
+                }
+            }
+            catch (IOException ioe)
+            {
+                // TODO: handle
+                ioe.printStackTrace (System.out);
+            }
+            
+
+            if (verbose)
+            {
+                if (mdata != null)
+                {
+                    log.verbose ("  merged metadata contains " + mdata.size () + " entries");
+                }
+                
+                if (cdata != null)
+                {
+                    log.verbose ("  merged coverage data contains " + cdata.size () + " entries");
+                }
+            }
+            
+            // write merged data into output file:
+            {
+                $assert.ASSERT (sdataOutFile != null, "sdataOutFile not null");
+                    
+                // the case of the output file being one of the input files is
+                // supported; however, for safety reasons we create output in
+                // a temp file and rename it only when the data is safely persisted:
+                
+                boolean rename = false;
+                File tempDataOutFile = null;
+                
+                final File canonicalDataOutFile = Files.canonicalizeFile (sdataOutFile);
+                
+                for (int f = 0; f < m_dataPath.length; ++ f)
+                {
+                    final File canonicalDataFile = Files.canonicalizeFile (m_dataPath [f]);
+                    if (canonicalDataOutFile.equals (canonicalDataFile))
+                    {
+                        rename = true;
+                        break;
+                    }
+                }
+                
+                if (rename) // create a temp out file
+                {
+                    File tempFileDir = canonicalDataOutFile.getParentFile ();
+                    if (tempFileDir == null) tempFileDir = new File ("");
+                    
+                    // length > 3:
+                    final String tempFileName = Files.getFileName (canonicalDataOutFile) + IAppConstants.APP_NAME_LC;
+                    final String tempFileExt = EMMAProperties.PROPERTY_TEMP_FILE_EXT;
+                
+                    try
+                    {
+                        tempDataOutFile = Files.createTempFile (tempFileDir, tempFileName, tempFileExt);
+                    }
+                    catch (IOException ioe)
+                    {
+                        // TODO: error code
+                        throw new EMMARuntimeException (ioe);
+                    }
+                    
+                    log.warning ("the specified output file is one of the input files [" + canonicalDataOutFile + "]");
+                    log.warning ("all merged data will be written to a temp file first [" + tempDataOutFile.getAbsolutePath ()  + "]");
+                }
+                
+                // persist merged session data:
+                {
+                    final long start = log.atINFO () ? System.currentTimeMillis () : 0;
+                    
+                    File persistFile = null;
+                    try
+                    {
+                        persistFile = tempDataOutFile != null ? tempDataOutFile : canonicalDataOutFile;
+                        
+                        // TODO: the persister API is ugly, redesign
+                        
+                        if ((mdata == null) || mdata.isEmpty ())
+                            DataFactory.persist (cdata, persistFile, false); // never merge to enforce compaction behavior
+                        else if ((cdata == null) || cdata.isEmpty ())
+                            DataFactory.persist (mdata, persistFile, false); // never merge to enforce compaction behavior
+                        else
+                            DataFactory.persist (new SessionData (mdata, cdata), persistFile, false); // never merge to enforce compaction behavior
+                    }
+                    catch (IOException ioe)
+                    {
+                        if (persistFile != null) persistFile.delete ();
+                        
+                        // TODO: error code
+                        throw new EMMARuntimeException (ioe);
+                    }
+                    catch (Error e)
+                    {
+                        if (persistFile != null) persistFile.delete ();
+                        
+                        throw e; // re-throw
+                    }
+                    
+                    if (rename) // rename-with-delete temp out file into the desired out file
+                    {
+                        if (! Files.renameFile (tempDataOutFile, canonicalDataOutFile, true)) // overwrite the original archive
+                        {
+                            // error code
+                            throw new EMMARuntimeException ("could not rename temporary file [" + tempDataOutFile.getAbsolutePath () + "] to [" + canonicalDataOutFile + "]: make sure the original file is not locked and can be deleted");
+                        }
+                    }
+                    
+                    if (log.atINFO ())
+                    {
+                        final long end = System.currentTimeMillis ();
+                        
+                        log.info ("merged/compacted data written to [" + canonicalDataOutFile + "] {in " + (end - start) + " ms}");
+                    }
+                }
+            }            
+        }
+        catch (SecurityException se)
+        {
+            failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
+        }
+        catch (RuntimeException re)
+        {
+            failure = re;
+        }
+        finally
+        {
+            reset ();
+        }
+        
+        if (failure != null)
+        {
+            if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES))
+            {
+                throw new EMMARuntimeException (UNEXPECTED_FAILURE,
+                                                new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK},
+                                                failure);
+            }
+            else
+                throw failure;
+        }
+    }
+
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private MergeProcessor ()
+    {
+        m_dataPath = IConstants.EMPTY_FILE_ARRAY;
+    }
+    
+    
+    private void reset ()
+    {
+        m_dataFileCount = 0;
+    }
+    
+    
+    // caller-settable state [scoped to this runner instance]:
+    
+    private File [] m_dataPath; // required to be non-null for run() [is set to canonicalized form]
+    private File m_sdataOutFile; // user override; can be null for run()
+
+    // internal run()-scoped state:
+    
+    private int m_dataFileCount;
+    
+    private static final Class [] EXPECTED_FAILURES; // set in <clinit>
+    
+    static
+    {
+        EXPECTED_FAILURES = new Class []
+        {
+            EMMARuntimeException.class,
+            IllegalArgumentException.class,
+            IllegalStateException.class,
+        };
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/MetaData.java b/core/java12/com/vladium/emma/data/MetaData.java
new file mode 100644
index 0000000..5416f8a
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/MetaData.java
@@ -0,0 +1,289 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: MetaData.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/*
+ * Average mem size/class entry: 6166 bytes [1.4.1, rt.jar], 5764 bytes [1.3.1, rt.jar]
+ */
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+final class MetaData implements IMetaData, Cloneable
+{
+    // public: ................................................................
+    
+    // TODO: MT-safety model
+    
+    // TODO: no duplicate detection is done here at the moment
+    // [may require supporting fast lookup for already added descriptors]
+    
+    public IMetaData shallowCopy ()
+    {
+        final MetaData _clone;
+        try
+        {
+            _clone = (MetaData) super.clone ();
+        }
+        catch (CloneNotSupportedException cnse)
+        {
+            throw new Error (cnse.toString ());
+        }
+        
+        final HashMap _classMap;
+        
+        synchronized (lock ())
+        {
+            _classMap = (HashMap) m_classMap.clone ();
+        }
+        
+        // [m_packagesWarned is not cloned by design]
+        
+        _clone.m_classMap = _classMap;
+        
+        return _clone;  
+    }
+    
+    public CoverageOptions getOptions ()
+    {
+        return m_options;
+    }
+    
+    public int size ()
+    {
+        return m_classMap.size (); 
+    }
+    
+    public boolean hasSrcFileData ()
+    {
+        return m_hasSrcFileInfo;
+    }
+    
+    public boolean hasLineNumberData ()
+    {
+        return m_hasLineNumberInfo;
+    }
+    
+    public Iterator iterator ()
+    {
+        return m_classMap.values ().iterator ();
+    }
+    
+//    public boolean hasDescriptor (final ClassDescriptor cls)
+//    {
+//        if ($assert.ENABLED) $assert.ASSERT (cls != null, "cls is null");
+//        
+//        return m_classes.contains (cls);
+//    }
+
+    public boolean hasDescriptor (final String classVMName)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (classVMName != null, "className is null");
+        
+        return m_classMap.containsKey (classVMName);
+    }
+    
+    public Object lock ()
+    {
+        return m_classMap;
+    }
+        
+    public boolean add (final ClassDescriptor cls, final boolean overwrite)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (cls != null, "cls is null");
+        
+        final String classVMName = cls.getClassVMName ();
+        
+        if (overwrite || ! m_classMap.containsKey (classVMName))
+        {
+            m_classMap.put (classVMName, cls);
+            
+            boolean incompleteDebugInfo = false;
+
+            if (! cls.hasSrcFileInfo ())
+            {
+                m_hasSrcFileInfo = false;
+                incompleteDebugInfo = true;
+            }
+            
+            if (! cls.hasCompleteLineNumberInfo ())
+            {
+                m_hasLineNumberInfo = false;
+                incompleteDebugInfo = true;
+            }
+            
+            // SF FR 971176: provide user with sample classes that may later
+            // caused warnings about line coverage not available
+            
+            if (incompleteDebugInfo)
+            {
+                final Logger log = Logger.getLogger ();
+                
+                if (log.atINFO ())
+                {
+                    final String packageVMName = cls.getPackageVMName ();
+                    
+                    if (m_packagesWarned.add (packageVMName))
+                    {
+                        log.info ("package [" + packageVMName + "] contains classes [" + cls.getName () + "] without full debug info");
+                    }
+                }
+            }
+            
+            return true;
+        }
+
+        return false;
+    }
+    
+    // IMergeable:
+    
+    public boolean isEmpty ()
+    {
+        return m_classMap.isEmpty ();
+    }
+    
+    /*
+     * note: rhs entries must override current entries
+     */
+    public IMergeable merge (final IMergeable rhs)
+    {
+        if ((rhs == null) || rhs.isEmpty () || (rhs == this))
+            return this;
+        else
+        {
+            final MetaData rhsmdata = (MetaData) rhs; // TODO: redesign to avoid this cast?
+            final Map rhsclasses = rhsmdata.m_classMap;
+            
+            // rhs entries always override existing content:
+            
+            for (Iterator entries = rhsclasses.entrySet ().iterator (); entries.hasNext (); )
+            {
+                final Map.Entry entry = (Map.Entry) entries.next ();
+                
+                final String classVMName = (String) entry.getKey ();
+                final Object rhsdescriptor = entry.getValue ();
+                    
+                m_classMap.put (classVMName, rhsdescriptor);
+            }
+            
+            // update debug info flags if necessary:
+            
+            if (! rhsmdata.hasSrcFileData ()) m_hasSrcFileInfo = false;
+            if (! rhsmdata.hasLineNumberData ()) m_hasLineNumberInfo = false;
+                
+            return this;
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    MetaData (final CoverageOptions options)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (options != null, "options is null");
+        m_options = options;
+        
+        m_hasSrcFileInfo = true;
+        m_hasLineNumberInfo = true;
+        
+        m_classMap = new HashMap ();
+        m_packagesWarned = new HashSet ();
+    }
+    
+    
+    static MetaData readExternal (final DataInput in)
+        throws IOException
+    {
+        final CoverageOptions options = CoverageOptions.readExternal (in);
+        
+        final boolean hasSrcFileInfo = in.readBoolean ();
+        final boolean hasLineNumberInfo = in.readBoolean ();
+        
+        final int size = in.readInt ();
+        final HashMap classMap = new HashMap (size);
+        
+        for (int i = 0; i < size; ++ i)
+        {
+            final String classVMName = in.readUTF ();
+            final ClassDescriptor cls = ClassDescriptor.readExternal (in);
+            
+            classMap.put (classVMName, cls);
+        }
+        
+        // [m_packagesWarned is not part of persisted state]
+        
+        return new MetaData (options, classMap, hasSrcFileInfo, hasLineNumberInfo);
+    }
+    
+    static void writeExternal (final MetaData mdata, final DataOutput out)
+        throws IOException
+    {
+        CoverageOptions.writeExternal (mdata.m_options, out);
+        
+        out.writeBoolean (mdata.m_hasSrcFileInfo);
+        out.writeBoolean (mdata.m_hasLineNumberInfo);
+        
+        final Map classMap = mdata.m_classMap;
+        
+        final int size = classMap.size ();
+        out.writeInt (size); // too bad the capacity is not visible
+        
+        final Iterator entries = classMap.entrySet ().iterator ();
+        for (int i = 0; i < size; ++ i)
+        {
+            final Map.Entry entry = (Map.Entry) entries.next ();
+            
+            final String classVMName = (String) entry.getKey ();
+            final ClassDescriptor cls = (ClassDescriptor) entry.getValue ();
+            
+            out.writeUTF (classVMName);
+            ClassDescriptor.writeExternal (cls, out);
+        }
+        
+        // [m_packagesWarned is not part of persisted state]
+    }
+    
+    // private: ...............................................................
+    
+    
+    private MetaData (final CoverageOptions options, final HashMap classMap,
+                      final boolean hasSrcFileInfo, final boolean hasLineNumberInfo)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (options != null, "options is null");
+        m_options = options;
+        
+        m_hasSrcFileInfo = hasSrcFileInfo;
+        m_hasLineNumberInfo = hasLineNumberInfo;
+        
+        m_classMap = classMap;
+    }
+    
+    
+    private final CoverageOptions m_options; // [never null]
+    private boolean m_hasSrcFileInfo, m_hasLineNumberInfo;
+    private /*final*/ HashMap /* classVMName:String->ClassDescriptor */ m_classMap; // [never null]
+    
+    private /*final*/ transient HashSet /*  packageVMName:String */ m_packagesWarned; // [never null]
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/MethodDescriptor.java b/core/java12/com/vladium/emma/data/MethodDescriptor.java
new file mode 100644
index 0000000..ac6cd25
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/MethodDescriptor.java
@@ -0,0 +1,327 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: MethodDescriptor.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.Serializable;
+
+import com.vladium.util.IConstants;
+import com.vladium.util.IntObjectMap;
+import com.vladium.util.IntSet;
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class MethodDescriptor implements IConstants, IMetadataConstants, Serializable
+{
+    // public: ................................................................    
+    
+    // need a separate 'blockCount' parm because 'blockMap' could be null
+    // and for a class that is never loaded I can't find out the number of
+    // blocks for block coverage reporting
+
+    public MethodDescriptor (final String name, final String descriptor, final int status,
+                             final int [] blockSizes, final int [][] blockMap, final int firstLine)
+    {
+        if (name == null)
+            throw new IllegalArgumentException ("null input: name");
+        if (descriptor == null)
+            throw new IllegalArgumentException ("null input: descriptor");
+        
+        if ((status & METHOD_NO_BLOCK_DATA) == 0)
+        {
+            // block metadata is available: blockCount must be positive
+            
+            final int blockCount = blockSizes.length;
+            
+            if ($assert.ENABLED) $assert.ASSERT (blockCount > 0, "blockCount must be positive: " + blockCount);
+            m_blockSizes = blockSizes;
+            
+            if ((status & METHOD_NO_LINE_DATA) == 0)
+            {
+                // line metadata is available: blockMap must not be null or empty
+                
+                if ($assert.ENABLED) $assert.ASSERT (firstLine > 0, "firstLine must be positive: " + firstLine);
+                
+                if ((blockMap == null) || (blockMap.length == 0))
+                    throw new IllegalArgumentException ("null or empty input: blockMap");
+            
+                if ($assert.ENABLED)
+                {
+                    $assert.ASSERT (blockCount == blockMap.length, "blockCount " + blockCount + " != blockMap.length " + blockMap.length);
+                    
+                    for (int i = 0; i < blockMap.length; ++ i)
+                    {
+                        $assert.ASSERT (blockMap [i] != null, "blockMap[" + i + "] is null");
+                        // note: it is legal for blockMap [i] to be empty
+                    }
+                }
+                
+                m_blockMap = blockMap;
+                m_firstLine = firstLine;
+            }
+            else
+            {
+                m_blockMap = null;
+                m_firstLine = 0;
+            }
+        }
+        else
+        {
+            m_blockSizes = null;
+            m_blockMap = null;
+            m_firstLine = 0;
+        }
+        
+        m_name = name;
+        m_descriptor = descriptor;
+        m_status = status;
+    }
+    
+    
+    public String getName ()
+    {
+        return m_name;
+    }
+    
+    public String getDescriptor ()
+    {
+        return m_descriptor;
+    }
+    
+    public int getStatus ()
+    {
+        return m_status;
+    }
+    
+    public int getBlockCount ()
+    {
+        return m_blockSizes.length;
+    }
+
+    public int [] getBlockSizes ()
+    {
+        return m_blockSizes;
+    }
+
+    public int [][] getBlockMap ()
+    {
+        return m_blockMap;
+    }
+    
+    public IntObjectMap /* line no->int[](blockIDs) */ getLineMap ()
+    {
+        IntObjectMap lineMap = m_lineMap;
+        if (lineMap != null)
+            return lineMap;
+        else if ((m_status & METHOD_NO_LINE_DATA) == 0)
+        {
+            // construct reverse line->block ID mapping:
+            
+            lineMap = new IntObjectMap ();
+            final int [][] blockMap = m_blockMap;
+            
+            for (int bl = 0, blCount = blockMap.length; bl < blCount; ++ bl)
+            {
+                final int [] lines = blockMap [bl];
+                if (lines != null)
+                {
+                    final int lineCount = lines.length;
+                    
+                    for (int l = 0; l < lineCount; ++ l)
+                    {
+                        final int line = lines [l];
+                        IntSet blockIDs = (IntSet) lineMap.get (line);
+                        
+                        if (blockIDs == null)
+                        {
+                            blockIDs = new IntSet ();
+                            lineMap.put (line, blockIDs);
+                        }
+                        
+                        blockIDs.add (bl);
+                    }
+                }
+            }
+            
+            final int [] lines = lineMap.keys ();
+            for (int l = 0, lineCount = lines.length; l < lineCount; ++ l)
+            {
+                final int line = lines [l];
+                final int [] blockIDs = ((IntSet) lineMap.get (line)).values ();
+                if ($assert.ENABLED) $assert.ASSERT (blockIDs != null && blockIDs.length > 0, "wrong line mapping for line #" + line);
+                
+                lineMap.put (line, blockIDs); // overwrite IntSet as the value
+            }
+            
+            m_lineMap = lineMap;
+            
+            return lineMap;
+        }
+        
+        return null;
+    }
+    
+    public int getFirstLine ()
+    {
+        return m_firstLine;
+    }
+    
+    public boolean hasLineNumberInfo ()
+    {
+        return (m_status & METHOD_NO_LINE_DATA) == 0;
+    }
+    
+    
+    public String toString ()
+    {
+        return toString ("");
+    }
+    
+    public String toString (final String indent)
+    {
+        StringBuffer s = new StringBuffer (indent + "method [" + m_name + "] descriptor:");
+        
+        if ((m_status & METHOD_NO_LINE_DATA) == 0)
+        {
+            for (int bl = 0; bl < m_blockMap.length; ++ bl)
+            {
+                s.append (EOL);
+                s.append (indent + INDENT_INCREMENT + "block " + bl + " (" + m_blockSizes [bl] + " instrs) : ");
+                
+                final int [] lines = m_blockMap [bl];
+                for (int l = 0; l < lines.length; ++ l)
+                {
+                    if (l != 0) s.append (", ");
+                    s.append (lines [l]);
+                }
+            }
+            s.append (EOL);
+            s.append (indent + INDENT_INCREMENT + "---");
+            
+            final int [] lines = m_lineMap.keys ();
+            for (int l = 0; l < lines.length; ++ l)
+            {
+                s.append (EOL);
+                s.append (indent + INDENT_INCREMENT + "line " + lines [l] + ": ");
+                
+                final int [] blocks = (int []) m_lineMap.get (lines [l]);
+                for (int bl = 0; bl < blocks.length; ++ bl)
+                {
+                    if (bl != 0) s.append (", ");
+                    s.append (blocks [bl]);
+                }
+            }
+        }
+        else
+        {
+            s.append (" <no line info>");
+        }
+        
+        return s.toString ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    static MethodDescriptor readExternal (final DataInput in)
+        throws IOException
+    {
+        final String name = in.readUTF ();
+        final String descriptor = in.readUTF ();
+        
+        final int status = in.readInt ();
+        
+        int [] blockSizes = null;
+        int [][] blockMap = null;
+        int firstLine = 0;
+        
+        if ((status & METHOD_NO_BLOCK_DATA) == 0)
+        {
+            // blockSizes must be set:
+            
+            blockSizes = DataFactory.readIntArray (in);
+            
+            if ((status & METHOD_NO_LINE_DATA) == 0)
+            {
+                // blockMap, lineMap, firstLine must be set:
+                
+                final int length = in.readInt ();
+                blockMap = new int [length][];
+                
+                for (int i = 0; i < length; ++ i) 
+                {
+                    blockMap [i] = DataFactory.readIntArray (in);
+                }
+                
+                firstLine = in.readInt ();
+                
+                // [lineMap is transient data]
+            }
+        }
+        
+        return new MethodDescriptor (name, descriptor, status, blockSizes, blockMap, firstLine);
+    }
+    
+    static void writeExternal (final MethodDescriptor method, final DataOutput out)
+        throws IOException
+    {
+        out.writeUTF (method.m_name);
+        out.writeUTF (method.m_descriptor);
+        
+        final int status = method.m_status;
+        out.writeInt (status);
+        
+        if ((status & METHOD_NO_BLOCK_DATA) == 0)
+        {
+            // blockSizes must be set:
+            
+            DataFactory.writeIntArray (method.m_blockSizes, out);
+            
+            if ((status & METHOD_NO_LINE_DATA) == 0)
+            {
+                // blockMap, lineMap, firstLine must be set:
+                
+                final int [][] blockMap = method.m_blockMap;
+                final int length = blockMap.length;
+                out.writeInt (length);
+
+                for (int i = 0; i < length; ++ i) 
+                {
+                    DataFactory.writeIntArray (blockMap [i], out);
+                }
+                
+                out.writeInt (method.m_firstLine);
+                
+                // [lineMap is transient data]
+            }
+        }
+    }
+    
+    // private: ...............................................................
+    
+    
+    private final String m_name; // internal JVM name (<init>, <clinit> for initializers, etc) [never null]
+    private final String m_descriptor; // [never null]
+    private final int m_status; // excluded, no debug data, etc
+    private final int [] m_blockSizes; // always of positive length if ((status & METHOD_NO_BLOCK_DATA) == 0)
+    private final int [][] m_blockMap; // [never null or empty if status is ...]
+    private final int m_firstLine; // 0 if not src line info is available
+    
+    private IntObjectMap /* line no->int[](blockIDs) */  m_lineMap; // created lazily [could be empty if status ...]
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/data/SessionData.java b/core/java12/com/vladium/emma/data/SessionData.java
new file mode 100644
index 0000000..e7b584a
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/SessionData.java
@@ -0,0 +1,53 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SessionData.java,v 1.1.1.1 2004/05/09 16:57:33 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.Serializable;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class SessionData implements ISessionData, Serializable 
+{
+    // public: ................................................................
+    
+    public IMetaData getMetaData ()
+    {
+        return m_mdata;
+    }
+    
+    public ICoverageData getCoverageData ()
+    {
+        return m_cdata;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    public SessionData (final IMetaData mdata, final ICoverageData cdata)
+    {
+        if (mdata == null) throw new IllegalArgumentException ("null input: mdata");
+        if (cdata == null) throw new IllegalArgumentException ("null input: cdata");
+        
+        m_mdata = mdata;
+        m_cdata = cdata;
+    }
+    
+    // private: ...............................................................
+    
+    
+    private final IMetaData m_mdata;
+    private final ICoverageData m_cdata;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/data/mergeCommand.java b/core/java12/com/vladium/emma/data/mergeCommand.java
new file mode 100644
index 0000000..5a98c23
--- /dev/null
+++ b/core/java12/com/vladium/emma/data/mergeCommand.java
@@ -0,0 +1,166 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: mergeCommand.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.data;
+
+import java.io.IOException;
+
+import com.vladium.util.ClassLoaderResolver;
+import com.vladium.util.args.IOptsParser;
+import com.vladium.emma.Command;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class mergeCommand extends Command
+{
+    // public: ................................................................
+
+    public mergeCommand (final String usageToolName, final String [] args)
+    {
+        super (usageToolName, args);
+    }
+
+    public synchronized void run ()
+    {
+        ClassLoader loader;
+        try
+        {
+            loader = ClassLoaderResolver.getClassLoader ();
+        }
+        catch (Throwable t)
+        {
+            loader = getClass ().getClassLoader ();
+        }
+        
+        try
+        {
+            // process 'args':
+            {
+                final IOptsParser parser = getOptParser (loader);
+                final IOptsParser.IOpts parsedopts = parser.parse (m_args);
+                
+                // check if usage is requested before checking args parse errors etc:
+                {
+                    final int usageRequestLevel = parsedopts.usageRequestLevel ();
+
+                    if (usageRequestLevel > 0)
+                    {
+                        usageexit (parser, usageRequestLevel, null);
+                        return;
+                    }
+                }
+                
+                final IOptsParser.IOpt [] opts = parsedopts.getOpts ();
+                
+                if (opts == null) // this means there were args parsing errors
+                {
+                    parsedopts.error (m_out, STDOUT_WIDTH);
+                    usageexit (parser, IOptsParser.SHORT_USAGE, null);
+                    return;
+                }
+                
+                // process parsed args:
+
+                try
+                {
+                    for (int o = 0; o < opts.length; ++ o)
+                    {
+                        final IOptsParser.IOpt opt = opts [o];
+                        final String on = opt.getCanonicalName ();
+                        
+                        if (! processOpt (opt))
+                        {
+                            if ("in".equals (on))
+                            {
+                                m_datapath = getListOptValue (opt, PATH_DELIMITERS, true);
+                            }
+                            else if ("out".equals (on))
+                            {
+                                m_outFileName = opt.getFirstValue ();
+                            }
+                        }
+                    }
+                    
+                    // user '-props' file property overrides:
+                    
+                    if (! processFilePropertyOverrides ()) return;
+                    
+                    // process prefixed opts:
+                    
+                    processCmdPropertyOverrides (parsedopts);
+                }
+                catch (IOException ioe)
+                {
+                    throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
+                }
+                
+                // handle cmd line-level defaults:
+                {
+                }
+            }
+            
+            // run the reporter:
+            {
+                final MergeProcessor processor = MergeProcessor.create ();
+                processor.setAppName (IAppConstants.APP_NAME); // for log prefixing
+                
+                processor.setDataPath (m_datapath);
+                processor.setSessionOutFile (m_outFileName);
+                processor.setPropertyOverrides (m_propertyOverrides);
+                
+                processor.run ();
+            }
+        }
+        catch (EMMARuntimeException yre)
+        {
+            // TODO: see below
+            
+            exit (true, yre.getMessage (), yre, RC_UNEXPECTED); // does not return
+            return;
+        }
+        catch (Throwable t)
+        {
+            // TODO: embed: OS/JVM fingerprint, build #, etc
+            // TODO: save stack trace in a file and prompt user to send it to ...
+            
+            exit (true, "unexpected failure: ", t, RC_UNEXPECTED); // does not return
+            return;
+        }
+
+        exit (false, null, null, RC_OK);
+    }    
+    
+    // protected: .............................................................
+
+
+    protected void initialize ()
+    {
+        super.initialize ();                
+    }
+    
+    protected String usageArgsMsg ()
+    {
+        return "[options]";
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private String [] m_datapath; // list of data files, not a real path
+    private String m_outFileName;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/filter/IInclExclFilter.java b/core/java12/com/vladium/emma/filter/IInclExclFilter.java
new file mode 100644
index 0000000..e366c23
--- /dev/null
+++ b/core/java12/com/vladium/emma/filter/IInclExclFilter.java
@@ -0,0 +1,222 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IInclExclFilter.java,v 1.1.1.1 2004/05/09 16:57:33 vlad_r Exp $
+ */
+package com.vladium.emma.filter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import com.vladium.util.WCMatcher;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, 2003
+ */
+public
+interface IInclExclFilter
+{
+    // public: ................................................................
+    
+    // TODO: move this into util pkg
+    
+    char INCLUSION_PREFIX = '+';
+    String INCLUSION_PREFIX_STRING = "+";
+    char EXCLUSION_PREFIX = '-';
+    String EXCLUSION_PREFIX_STRING = "-";
+
+    boolean included (final String s);
+
+    abstract class Factory
+    {
+        public static IInclExclFilter create (final String specs1, final String separators, final String [] specs2)
+        {
+            if ((specs1 == null) || (specs1.trim ().length () == 0))
+                return create (specs2);
+            else
+            {
+                final List /* String */ _specs = new ArrayList ();
+                
+                if (specs2 != null)
+                {
+                    for (int s = 0; s < specs2.length; ++ s)
+                    {
+                        _specs.add (specs2 [s]);
+                    }
+                }
+                
+                for (StringTokenizer tokenizer = new StringTokenizer (specs1, separators);
+                     tokenizer.hasMoreTokens (); )
+                {
+                    _specs.add (tokenizer.nextToken ());
+                }
+                
+                final String [] specs = new String [_specs.size ()];
+                _specs.toArray (specs);
+                
+                return create (specs);
+            }
+        }
+        
+        public static IInclExclFilter create (final String [] specs)
+        {
+            if ((specs == null) || (specs.length == 0))
+                return new WCInclExclFilter ((String []) null, (String []) null);
+            
+            final List inclusions = new ArrayList ();
+            final List exclusions = new ArrayList ();
+            
+            for (int i = 0, iLimit = specs.length; i < iLimit; ++ i)
+            {
+                final String spec = specs [i];
+                
+                if (spec.length () > 0)
+                {
+                    if (spec.charAt (0) == EXCLUSION_PREFIX)
+                        exclusions.add (spec.substring (1));
+                    else
+                    {
+                        // [inclusion prefix is optional]
+                        
+                        if (spec.charAt (0) == INCLUSION_PREFIX)
+                            inclusions.add (spec.substring (1));
+                        else
+                            inclusions.add (spec);
+                    }
+                } 
+            }
+            
+            return new WCInclExclFilter (inclusions, exclusions);
+        }
+        
+        public static IInclExclFilter create (final String [] inclusions,
+                                              final String [] exclusions)
+        {
+            return new WCInclExclFilter (inclusions, exclusions);
+        }
+        
+        public static IInclExclFilter create (final List /* String */ inclusions,
+                                              final List /* String */ exclusions)
+        {
+            return new WCInclExclFilter (inclusions, exclusions);
+        }
+
+        private static final class WCInclExclFilter implements IInclExclFilter
+        {
+            public boolean included (final String s)
+            {
+                if (s == null) return false;
+                
+                final char [] chars = s.toCharArray ();
+                
+                // included set is (inclusions - exclusions), where null inclusions
+                // mean 'everything' and null exclusions mean 'nothing':
+                
+                final WCMatcher [] inclusions = m_inclusions;
+                final WCMatcher [] exclusions = m_exclusions;
+                
+                if (inclusions != null)
+                {
+                    boolean included = false;
+                    
+                    for (int i = 0, iLimit = inclusions.length; i < iLimit; ++ i)
+                    {
+                        if (inclusions [i].matches (chars))
+                        {
+                            included = true;
+                            break;
+                        }
+                    }
+                    
+                    if (! included) return false;
+                }
+    
+                if (exclusions != null)
+                {
+                    for (int x = 0, xLimit = exclusions.length; x < xLimit; ++ x)
+                    {
+                        if (exclusions [x].matches (chars)) return false;
+                    }
+                }
+                
+                return true;
+            }
+            
+            
+            WCInclExclFilter (final String [] inclusions,
+                              final String [] exclusions)
+            {
+                if ((inclusions == null) || (inclusions.length == 0))
+                    m_inclusions = null;
+                else
+                {
+                    m_inclusions = new WCMatcher [inclusions.length];
+                    
+                    for (int i = 0; i < inclusions.length; ++ i)
+                    {
+                        m_inclusions [i] = WCMatcher.compile (inclusions [i]);
+                    }
+                }
+                
+                if ((exclusions == null) || (exclusions.length == 0))
+                    m_exclusions = null;
+                else
+                {
+                    m_exclusions = new WCMatcher [exclusions.length];
+                    
+                    for (int i = 0; i < exclusions.length; ++ i)
+                    {
+                        m_exclusions [i] = WCMatcher.compile (exclusions [i]);
+                    }
+                }
+            }
+            
+            WCInclExclFilter (final List /* String */ inclusions,
+                              final List /* String */ exclusions)
+            {
+                if ((inclusions == null) || inclusions.isEmpty ())
+                    m_inclusions = null;
+                else
+                {
+                    m_inclusions = new WCMatcher [inclusions.size ()];
+                    
+                    int ii = 0;
+                    for (Iterator i = inclusions.iterator (); i.hasNext (); ++ ii)
+                    {
+                        final String pattern = (String) i.next ();
+                        
+                        m_inclusions [ii] = WCMatcher.compile (pattern);
+                    }
+                }
+                
+                if ((exclusions == null) || exclusions.isEmpty ())
+                    m_exclusions = null;
+                else
+                {
+                    m_exclusions = new WCMatcher [exclusions.size ()];
+                    
+                    int ii = 0;
+                    for (Iterator i = exclusions.iterator (); i.hasNext (); ++ ii)
+                    {
+                        final String pattern = (String) i.next ();
+                        
+                        m_exclusions [ii] = WCMatcher.compile (pattern);
+                    }
+                }
+            }
+            
+            
+            private final WCMatcher [] m_inclusions, m_exclusions;
+            
+        } // end of nested class
+        
+    } // end of nested class
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/instr/InstrProcessor.java b/core/java12/com/vladium/emma/instr/InstrProcessor.java
new file mode 100644
index 0000000..d8e2f2f
--- /dev/null
+++ b/core/java12/com/vladium/emma/instr/InstrProcessor.java
@@ -0,0 +1,319 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InstrProcessor.java,v 1.1.1.1.2.3 2004/07/17 16:57:14 vlad_r Exp $
+ */
+package com.vladium.emma.instr;
+
+import java.io.File;
+
+import com.vladium.util.Files;
+import com.vladium.util.IConstants;
+import com.vladium.util.IPathEnumerator;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.Processor;
+import com.vladium.emma.filter.IInclExclFilter;
+
+// ----------------------------------------------------------------------------
+/*
+ * This class was not meant to be public by design. It is made to to work around
+ * access bugs in reflective invocations. 
+ */
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class InstrProcessor extends Processor
+                              implements IPathEnumerator.IPathHandler
+{
+    // public: ................................................................
+    
+    
+    public static final String PROPERTY_EXCLUDE_SYNTHETIC_METHODS   = "instr.exclude_synthetic_methods";
+    public static final String PROPERTY_EXCLUDE_BRIDGE_METHODS      = "instr.exclude_bridge_methods";
+    public static final String PROPERTY_DO_SUID_COMPENSATION        = "instr.do_suid_compensation";
+    
+    public static final String DEFAULT_EXCLUDE_SYNTHETIC_METHODS    = "true";
+    public static final String DEFAULT_EXCLUDE_BRIDGE_METHODS       = "true";
+    public static final String DEFAULT_DO_SUID_COMPENSATION         = "true";
+    
+    public static InstrProcessor create ()
+    {
+        return new InstrProcessorST ();
+    }
+
+    /**
+     * 
+     * @param path [null is equivalent to an empty array]
+     * @param canonical
+     */
+    public synchronized final void setInstrPath (final String [] path, final boolean canonical)
+    {
+        if ((path == null) || (path.length == 0))
+            m_instrPath = IConstants.EMPTY_FILE_ARRAY;
+        else
+            m_instrPath = Files.pathToFiles (path, canonical);
+            
+        m_canonical = canonical;
+    }
+    
+    public synchronized final void setDependsMode (final boolean enable)
+    {
+        m_dependsMode = enable;
+    } 
+    
+    /**
+     * 
+     * @param specs [null is equivalent to no filtering (everything is included)] 
+     */
+    public synchronized final void setInclExclFilter (final String [] specs)
+    {
+        if (specs == null)
+            m_coverageFilter = null;
+        else
+            m_coverageFilter = IInclExclFilter.Factory.create (specs);
+    }
+    
+    /**
+     * 
+     * @param fileName [null unsets the previous override setting]
+     */
+    public synchronized final void setMetaOutFile (final String fileName)
+    {
+        if (fileName == null)
+            m_mdataOutFile = null;
+        else
+        {
+            final File _file = new File (fileName);
+                
+            if (_file.exists () && ! _file.isFile ())
+                throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]");
+                
+            m_mdataOutFile = _file;
+        }
+    }
+    
+    /**
+     * 
+     * @param merge [null unsets the previous override setting]
+     */
+    public synchronized final void setMetaOutMerge (final Boolean merge)
+    {
+        m_mdataOutMerge = merge;
+    }
+    
+    /**
+     * 
+     * @param dir [null unsets the previous setting]
+     */
+    public synchronized final void setInstrOutDir (final String dir)
+    {
+        if (dir == null)
+            m_outDir = null;
+        else
+        {
+            final File _outDir = new File (dir);
+            if (_outDir.exists () && ! _outDir.isDirectory ())
+                throw new IllegalArgumentException ("not a directory: [" + _outDir.getAbsolutePath () + "]");
+                
+            m_outDir = _outDir;
+        }
+    }
+    
+    /**
+     * 
+     * @param mode [may not be null]
+     */
+    public synchronized final void setOutMode (final OutMode mode)
+    {
+        if (mode == null)
+            throw new IllegalArgumentException ("null input: mode");
+            
+        m_outMode = mode;
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected InstrProcessor ()
+    {
+        m_dependsMode = true;
+    }
+
+
+    protected void validateState ()
+    {
+        super.validateState ();
+        
+        if ((m_instrPath == null) || (m_instrPath.length == 0))
+            throw new IllegalStateException ("instrumentation path not set");
+            
+        // [m_coverageFilter can be null]
+
+        if (m_outMode == null)
+            throw new IllegalStateException ("output mode not set");
+        
+        if (m_outMode != OutMode.OUT_MODE_OVERWRITE)
+        { 
+            if (m_outDir == null)
+                throw new IllegalStateException ("output directory not set");
+                        
+            // for non-overwrite modes output directory must not overlap
+            // with the instr path:
+            
+            // [the logic below does not quite catch all possibilities due to
+            // Class-Path: manifest attributes and dir nesting, but it should
+            // intercept most common mistakes]
+            
+            if ($assert.ENABLED)
+            {
+                $assert.ASSERT (m_outDir != null, "m_outDir = null");
+                $assert.ASSERT (m_instrPath != null, "m_instrPath = null");
+            }
+            
+            final File canonicalOutDir = Files.canonicalizeFile (m_outDir);
+            final File [] canonicalInstrPath;
+            
+            if (m_canonical)
+                canonicalInstrPath = m_instrPath;
+            else
+            {
+                canonicalInstrPath = new File [m_instrPath.length];
+                
+                for (int ip = 0; ip < canonicalInstrPath.length; ++ ip)
+                {
+                    canonicalInstrPath [ip] = Files.canonicalizeFile (m_instrPath [ip]);
+                }
+            }
+            
+            // FR_SF988785: detect if the user attempted to use a parent of m_outDir as one of
+            // the input directories (prevents spurious "already instrumented" errors)
+            
+            final int instrPathLength = canonicalInstrPath.length;
+            for (File dir = canonicalOutDir; dir != null; dir = dir.getParentFile ()) // getParentFile() does no real I/O
+            {
+                for (int ip = 0; ip < instrPathLength; ++ ip)
+                {
+                    if (dir.equals (canonicalInstrPath [ip]))
+                        throw new IllegalStateException ("output directory [" + canonicalOutDir + "] cannot be one of the instrumentation path directories (or a child thereof)");
+                }
+            }
+        }
+        
+        // [m_mdataOutFile can be null]
+        // [m_mdataOutMerge can be null]
+    }
+    
+    protected void reset ()
+    {
+        m_classCopies = m_classInstrs = 0;
+    }  
+    
+    protected final void createDir (final File dir, final boolean mkall)
+        throws EMMARuntimeException
+    {
+        if (mkall)
+        {
+            if (! dir.mkdirs () && ! dir.exists ())
+                throw new EMMARuntimeException (IAppErrorCodes.OUT_MKDIR_FAILURE, new Object [] {dir.getAbsolutePath ()}); 
+        }
+        else
+        {
+            if (! dir.mkdir () && ! dir.exists ())
+                throw new EMMARuntimeException (IAppErrorCodes.OUT_MKDIR_FAILURE, new Object [] {dir.getAbsolutePath ()});
+        } 
+    }
+
+    protected final File getFullOutDir (final File pathDir, final boolean isClass)
+    {
+        if (m_outMode == OutMode.OUT_MODE_OVERWRITE)
+        {
+            return pathDir;
+        }
+        else if (m_outMode == OutMode.OUT_MODE_COPY)
+        {
+            return m_outDir;
+        }
+        else if (m_outMode == OutMode.OUT_MODE_FULLCOPY)
+        {
+            return isClass ? Files.newFile (m_outDir, CLASSES) : Files.newFile (m_outDir, LIB);
+        }
+        else throw new IllegalStateException ("invalid out mode state: " + m_outMode);
+    }
+
+    protected final File getFullOutFile (final File pathDir, final File file, final boolean isClass)
+    {
+        return Files.newFile (getFullOutDir (pathDir, isClass), file.getPath ());
+    } 
+
+
+    // caller-settable state [scoped to this runner instance]:
+    
+    protected File [] m_instrPath; // required to be non-null/non-empty for run()
+    protected boolean m_dependsMode;
+    protected boolean m_canonical;
+    protected IInclExclFilter m_coverageFilter; // can be null for run()
+    protected OutMode m_outMode; // required to be set for run()
+    protected File m_outDir; // required to be non-null for run(), unless output mode is 'overwrite'
+    protected File m_mdataOutFile; // user override; can be null for run()
+    protected Boolean m_mdataOutMerge; // user override; can be null for run()
+    
+    // internal run()-scoped state:
+    
+    protected int m_classCopies, m_classInstrs;
+
+    protected static final String CLASSES   = "classes";
+    protected static final String LIB       = "lib";
+    protected static final boolean IN_CLASSES   = true;
+    protected static final boolean IN_LIB       = ! IN_CLASSES;
+
+    // package: ...............................................................
+    
+// TODO: access level [public to workaround Sun's bugs in access level in reflective invocations]
+    public static final class OutMode
+    {
+        public static final OutMode OUT_MODE_COPY = new OutMode ("copy");
+        public static final OutMode OUT_MODE_FULLCOPY = new OutMode ("fullcopy");
+        public static final OutMode OUT_MODE_OVERWRITE = new OutMode ("overwrite");
+        
+        public String getName ()
+        {
+            return m_name;
+        }
+        
+        public String toString ()
+        {
+            return m_name;
+        }
+        
+        public static OutMode nameToMode (final String name)
+        {
+            if (OUT_MODE_COPY.m_name.equals (name))
+                return OUT_MODE_COPY;
+            else if (OUT_MODE_FULLCOPY.m_name.equals (name))
+                return OUT_MODE_FULLCOPY;
+            else if (OUT_MODE_OVERWRITE.m_name.equals (name))
+                return OUT_MODE_OVERWRITE;
+            
+            return null;
+        }
+        
+        private OutMode (final String name)
+        {
+            m_name = name;
+        }
+        
+        
+        private final String m_name;
+        
+    } // end of nested class
+            
+    // private: ...............................................................
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/instr/InstrProcessorST.java b/core/java12/com/vladium/emma/instr/InstrProcessorST.java
new file mode 100644
index 0000000..a7ee0c8
--- /dev/null
+++ b/core/java12/com/vladium/emma/instr/InstrProcessorST.java
@@ -0,0 +1,1054 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InstrProcessorST.java,v 1.1.1.1.2.3 2004/07/16 23:32:28 vlad_r Exp $
+ */
+package com.vladium.emma.instr;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Date;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.compiler.ClassWriter;
+import com.vladium.jcd.parser.ClassDefParser;
+import com.vladium.logging.Logger;
+import com.vladium.util.ByteArrayOStream;
+import com.vladium.util.Descriptors;
+import com.vladium.util.Files;
+import com.vladium.util.IPathEnumerator;
+import com.vladium.util.IProperties;
+//import com.vladium.util.Profiler;
+import com.vladium.util.Property;
+import com.vladium.util.asserts.$assert;
+import com.vladium.util.exception.Exceptions;
+//import com.vladium.utils.ObjectSizeProfiler;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.CoverageOptions;
+import com.vladium.emma.data.CoverageOptionsFactory;
+import com.vladium.emma.data.DataFactory;
+import com.vladium.emma.data.IMetaData;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+final class InstrProcessorST extends InstrProcessor
+                             implements IAppErrorCodes
+{
+    // public: ................................................................
+
+    // TODO: performance of 'copy' mode could be improved by pushing dir filtering
+    // all the way to the path enumerator [although dir listing is reasonably fast] 
+        
+    
+    // IPathEnumerator.IPathHandler:
+    
+    
+    public final void handleArchiveStart (final File parentDir, final File archive, final Manifest manifest)
+    {
+        final Logger log = m_log;
+        if (log.atTRACE2 ()) log.trace2 ("handleArchiveStart", "[" + parentDir + "] [" + archive + "]");
+        
+        // TODO: pass manifest into this callback, if any
+        // TODO: detect if manifest corresonds to a previously intrumented archive already ?
+        
+        if (DO_DEPENDS_CHECKING)
+        {
+            final File fullArchiveFile = Files.newFile (parentDir, archive);
+            m_currentArchiveTS = fullArchiveFile.lastModified ();
+            
+            if ($assert.ENABLED) $assert.ASSERT (m_currentArchiveTS > 0, "invalid ts: " + m_currentArchiveTS);
+        }
+        
+        if ((m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE))
+        {
+            final Manifest outManifest = manifest != null
+                ? new Manifest (manifest) // shallow copy
+                : new Manifest (); 
+            
+            // set some basic main attributes:
+            
+            final Attributes mainAttrs = outManifest.getMainAttributes ();
+            if (manifest == null) mainAttrs.put (Attributes.Name.MANIFEST_VERSION,  "1.0");
+            mainAttrs.put (new Attributes.Name ("Created-By"), IAppConstants.APP_NAME + " v" + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG);
+                            
+            // note: Manifest makes these 72-char-safe
+            
+            mainAttrs.put (Attributes.Name.IMPLEMENTATION_TITLE,  "instrumented version of [" + archive.getAbsolutePath () + "]");
+            mainAttrs.put (Attributes.Name.SPECIFICATION_TITLE,  "instrumented on " + new Date (m_timeStamp) + " [" + Property.getSystemFingerprint () + "]");
+            
+            // TODO: remove entries related to signing            
+        
+            if (m_outMode == OutMode.OUT_MODE_FULLCOPY)
+            {
+                // create an identically named artive in outdir/lib [the stream is
+                // closed in the archive end event handler]:
+                
+                try
+                {
+                    final OutputStream out = new FileOutputStream (getFullOutFile (parentDir, archive, IN_LIB));
+                    
+                    m_archiveOut = outManifest != null ? new JarOutputStream (out, outManifest) : new JarOutputStream (out);
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+            }
+            else if (m_outMode == OutMode.OUT_MODE_OVERWRITE)
+            {
+                // create a temp file in the same dir [moved into the original one
+                // in the archive end event handler]:
+                
+                m_origArchiveFile = Files.newFile (parentDir, archive);
+                
+                // length > 3:
+                final String archiveName = Files.getFileName (archive) + IAppConstants.APP_NAME_LC;
+                final String archiveExt = EMMAProperties.PROPERTY_TEMP_FILE_EXT;
+                
+                try
+                {
+                    m_tempArchiveFile = Files.createTempFile (parentDir, archiveName, archiveExt);
+                    if (log.atTRACE2 ()) log.trace2 ("handleArchiveStart", "created temp archive [" + m_tempArchiveFile.getAbsolutePath () + "]");
+                    
+                    final OutputStream out = new FileOutputStream (m_tempArchiveFile);
+                    
+                    m_archiveOut = outManifest != null ? new JarOutputStream (out, outManifest) : new JarOutputStream (out);
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+            }
+        }
+    }
+
+    public final void handleArchiveEntry (final JarInputStream in, final ZipEntry entry)
+    {
+        final Logger log = m_log;
+        if (log.atTRACE2 ()) log.trace2 ("handleArchiveEntry", "[" + entry.getName () + "]");
+        
+        final String name = entry.getName ();
+        final String lcName = name.toLowerCase ();
+
+        final boolean notcopymode = (m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE);
+        
+        boolean copyEntry = false;
+
+        if (lcName.endsWith (".class"))
+        {
+            final String className = name.substring (0, name.length () - 6).replace ('/', '.');
+            
+            // it is possible that a class with this name has already been processed;
+            // however, we can't skip it here because there is no guarantee that
+            // the runtime classpath will be identical to the instrumentation path
+            
+            // [the metadata will still contain only a single entry for a class with
+            // this name: it is the responsibility of the user to ensure that both
+            // files represent the same class; in the future I might use a more
+            // robust internal strategy that uses something other than a class name
+            // as a metadata key]
+            
+            if ((m_coverageFilter == null) || m_coverageFilter.included (className))
+            {
+                InputStream clsin = null;
+                try
+                {
+                    File outFile = null;
+                    File fullOutFile = null;
+                    
+                    if (DO_DEPENDS_CHECKING)
+                    {
+                        // in 'copy' mode 
+                        
+                        if (m_outMode == OutMode.OUT_MODE_COPY)
+                        {
+                            outFile = new File (className.replace ('.', File.separatorChar).concat (".class"));
+                            fullOutFile = getFullOutFile (null, outFile, IN_CLASSES);
+                            
+                            // if we already processed this class name within this instrumentor
+                            // run, skip duplicates in copy mode: 
+                            
+                            if (m_mdata.hasDescriptor (Descriptors.javaNameToVMName (className)))
+                                return;
+                            
+                            // BUG_SF989071: using outFile here instead resulted in
+                            // a zero result regardless of whether the target existed or not
+                            final long outTimeStamp = fullOutFile.lastModified (); // 0 if 'fullOutFile' does not exist or if an I/O error occurs
+                             
+                            if (outTimeStamp > 0)
+                            {
+                                long inTimeStamp = entry.getTime (); // can return -1
+                                if (inTimeStamp < 0) inTimeStamp = m_currentArchiveTS; // default to the archive file timestamp
+                                
+                                if ($assert.ENABLED) $assert.ASSERT (inTimeStamp > 0);
+                                
+                                if (inTimeStamp <= outTimeStamp)
+                                {
+                                    if (log.atVERBOSE ()) log.verbose ("destination file [" + outFile + "] skipped: more recent than the source");
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                    
+                    readZipEntry (in, entry);
+                    
+                    final ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos);
+                    
+                    m_visitor.process (clsDef, m_outMode == OutMode.OUT_MODE_OVERWRITE, true, true, m_instrResult);
+                    if (m_instrResult.m_instrumented)
+                    {
+                        if ($assert.ENABLED) $assert.ASSERT (m_instrResult.m_descriptor != null, "no descriptor created for an instrumented class");
+                        
+                        ++ m_classInstrs;
+                        
+                        // update metadata [if this class has not been seen before]:
+                        
+                        m_mdata.add (m_instrResult.m_descriptor, false);
+                        
+                        // class def modified: write it to an array and submit a write job
+                        
+                        m_baos.reset ();
+                        ClassWriter.writeClassTable (clsDef, m_baos);
+                        
+                        if (notcopymode)
+                        {
+                            // [destination is a zip entry]
+                            
+                            entry.setTime (m_timeStamp);
+                            addJob (new EntryWriteJob (m_archiveOut, m_baos.copyByteArray (), entry, false));
+                        }
+                        else // copy mode
+                        {
+                            // [destination is a file]
+                            
+                            if (! DO_DEPENDS_CHECKING) // this block is just a complement to the one above (where fullOutFile is inited)
+                            {
+                                outFile = new File (className.replace ('.', File.separatorChar).concat (".class"));
+                                fullOutFile = getFullOutFile (null, outFile, IN_CLASSES);
+                            }
+                        
+                            addJob (new FileWriteJob (fullOutFile, m_baos.copyByteArray (), true));
+                        }
+                    }   
+                    else if (notcopymode)
+                    {
+                        // original class def already read into m_readbuf:
+                        // clone the array and submit an entry write job
+                         
+                        final byte [] data = new byte [m_readpos];
+                        System.arraycopy (m_readbuf, 0, data, 0, data.length);
+                        ++ m_classCopies;
+                        
+                        entry.setTime (m_timeStamp);
+                        addJob (new EntryWriteJob (m_archiveOut, data, entry, true));
+                    }
+                }
+                catch (FileNotFoundException fnfe)
+                {
+                    // ignore: this should never happen
+                    if ($assert.ENABLED)
+                    {
+                        fnfe.printStackTrace (System.out);
+                    }
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+                finally
+                {
+                    if (clsin != null)
+                        try
+                        {
+                            clsin.close ();
+                        }
+                        catch (Exception e)
+                        {
+                            // TODO: error code
+                            throw new EMMARuntimeException (e);
+                        }
+                }
+            }
+            else
+            {
+                // copy excluded .class entries in full copy and overwrite modes:
+                copyEntry = notcopymode;
+            }
+        }
+        else
+        {
+            // copy non-.class entries in full copy and overwrite modes:
+            copyEntry = notcopymode;
+            
+            // skipping these entries here is important: this is done as a complement
+            // to Sun jar API workarounds as detailed in PathEnumerator.enumeratePathArchive():
+            
+            if (copyEntry && name.equalsIgnoreCase ("META-INF/"))
+                copyEntry = false;
+            if (copyEntry && name.equalsIgnoreCase (JarFile.MANIFEST_NAME))
+                copyEntry = false;
+                
+            // TODO: skip signature-related entries (.SF and .RSA/.DSA/.PGP)
+        }
+        
+        if (copyEntry)
+        {
+            try
+            {
+                readZipEntry (in, entry);
+                
+                final byte [] data = new byte [m_readpos];
+                System.arraycopy (m_readbuf, 0, data, 0, data.length);
+                ++ m_classCopies;
+                
+                entry.setTime (m_timeStamp);
+                addJob (new EntryWriteJob (m_archiveOut, data, entry, true));
+            }
+            catch (IOException ioe)
+            {
+                // TODO: error code
+                throw new EMMARuntimeException (ioe);
+            }
+        }
+    }
+        
+    public final void handleArchiveEnd (final File parentDir, final File archive)
+    {
+        final Logger log = m_log;
+        if (log.atTRACE2 ()) log.trace2 ("handleArchiveEnd", "[" + parentDir + "] [" + archive + "]");
+        
+        m_currentArchiveTS = Long.MAX_VALUE;
+        
+        if ((m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE))
+        {
+            try
+            {
+                drainJobQueue (); // drain the queue before closing the archive
+                
+                m_archiveOut.flush ();
+                m_archiveOut.close ();
+                m_archiveOut = null;
+            }
+            catch (IOException ioe)
+            {
+                // TODO: error code
+                throw new EMMARuntimeException (ioe);
+            }
+            
+            // in overwrite mode replace the original archive with the temp archive:
+             
+            if (m_outMode == OutMode.OUT_MODE_OVERWRITE)
+            {
+                if (! Files.renameFile (m_tempArchiveFile, m_origArchiveFile, true)) // overwrite the original archive
+                {
+                    // TODO: disable temp file cleanup in this case so that the user
+                    // could do it manually later?
+                    
+                    // error code
+                    throw new EMMARuntimeException ("could not rename temporary file [" + m_tempArchiveFile + "] to [" + m_origArchiveFile + "]: make sure the original file is not locked and can be deleted");
+                }
+                else
+                {
+                    if (log.atTRACE2 ()) log.trace2 ("handleArchiveEnd", "renamed temp archive [" + m_tempArchiveFile.getAbsolutePath () + "] to [" + m_origArchiveFile + "]");
+                    m_origArchiveFile = m_tempArchiveFile = null;
+                }
+            }
+        }
+    }
+
+
+    public final void handleDirStart (final File pathDir, final File dir)
+    {
+        final Logger log = m_log;
+        if (log.atTRACE2 ()) log.trace2 ("handleDirStart", "[" + pathDir + "] [" + dir + "]");
+        
+        // in full copy mode, create all dirs here; in copy mode, do it as part
+        // of writing each individual file:
+        
+        if (m_outMode == OutMode.OUT_MODE_FULLCOPY)
+        {
+            final File saveDir = new File (getFullOutDir (pathDir, IN_CLASSES), dir.getPath ());
+            createDir (saveDir, true);
+        }
+    }
+
+    public final void handleFile (final File pathDir, final File file)
+    {
+        final Logger log = m_log;
+        if (log.atTRACE2 ()) log.trace2 ("handleFile", "[" + pathDir + "] [" + file + "]");
+        
+        final String name = file.getPath ();
+        final String lcName = name.toLowerCase ();
+
+        final boolean fullcopymode = (m_outMode == OutMode.OUT_MODE_FULLCOPY);
+        final boolean mkdir = (m_outMode == OutMode.OUT_MODE_COPY);
+        
+
+        boolean copyFile = false;
+
+        if (lcName.endsWith (".class"))
+        {
+            final String className = name.substring (0, name.length () - 6).replace (File.separatorChar, '.');
+            
+            // it is possible that a class with this name has already been processed;
+            // however, we can't skip it here because there is no guarantee that
+            // the runtime classpath will be identical to the instrumentation path
+            
+            // [the metadata will still contain only a single entry for a class with
+            // this name: it is the responsibility of the user to ensure that both
+            // files represent the same class; in the future I might use a more
+            // robust internal strategy that uses something other than a class name
+            // as a metadata key]
+            
+            if ((m_coverageFilter == null) || m_coverageFilter.included (className))
+            {
+                InputStream clsin = null;
+                try
+                {
+                    final File inFile = Files.newFile (pathDir, file.getPath ());
+                    final File fullOutFile = getFullOutFile (pathDir, file, IN_CLASSES);
+                    
+                    if (DO_DEPENDS_CHECKING)
+                    {
+                        if (m_outMode == OutMode.OUT_MODE_COPY)
+                        {
+                            // if we already processed this class name within this instrumentor
+                            // run, skip duplicates in copy mode: 
+                            
+                            if (m_mdata.hasDescriptor (Descriptors.javaNameToVMName (className)))
+                                return;
+                            
+                            // otherwise, instrument only if the dest file is out of date
+                            // wrt to the source file:
+                            
+                            final long outTimeStamp = fullOutFile.lastModified (); // 0 if 'fullOutFile' does not exist or if an I/O error occurs
+                             
+                            if (outTimeStamp > 0)
+                            {
+                                final long inTimeStamp = inFile.lastModified ();
+                                
+                                if (inTimeStamp <= outTimeStamp)
+                                {
+                                    if (log.atVERBOSE ()) log.verbose ("destination file [" + fullOutFile + "] skipped: more recent that the source file");
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                    
+                    readFile (inFile);
+                    
+                    ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos);
+
+                    // in non-overwrite modes, bail if src file already instrumented:
+                    m_visitor.process (clsDef, m_outMode == OutMode.OUT_MODE_OVERWRITE, true, true, m_instrResult);
+                    if (m_instrResult.m_instrumented)
+                    {
+                        if ($assert.ENABLED) $assert.ASSERT (m_instrResult.m_descriptor != null, "no descriptor created for an instrumented class");
+                        
+                        ++ m_classInstrs;
+                        
+                        // update metadata [if this class has not been seen before]:
+       
+//       ObjectSizeProfiler.SizeProfile profile = ObjectSizeProfiler.profile (m_instrResult.m_descriptor, true);
+//       System.out.println (clsDef.getName () + " metadata:");
+//       System.out.println (profile.root ().dump (0.2));
+                        
+                        m_mdata.add (m_instrResult.m_descriptor, false);
+
+                        // class def modified: write it to an array and submit a write job
+                        
+                        m_baos.reset ();
+                        ClassWriter.writeClassTable (clsDef, m_baos);
+                        clsDef = null;
+                                                
+                        final byte [] outdata = m_baos.copyByteArray ();
+                        
+                        addJob (new FileWriteJob (fullOutFile, outdata, mkdir));
+                    }   
+                    else if (fullcopymode)
+                    {
+                        // original class def already read into m_readbuf:
+                        // clone the array and submit a file write job
+                        
+                        clsDef = null;
+                        
+                        final byte [] outdata = new byte [m_readpos];
+                        System.arraycopy (m_readbuf, 0, outdata, 0, m_readpos);
+                        ++ m_classCopies;
+                        
+                        addJob (new FileWriteJob (fullOutFile, outdata, mkdir));
+                    }
+                }
+                catch (FileNotFoundException fnfe)
+                {
+                    // ignore: this should never happen
+                    if ($assert.ENABLED)
+                    {
+                        fnfe.printStackTrace (System.out);
+                    }
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+                finally
+                {
+                    if (clsin != null)
+                        try
+                        {
+                            clsin.close ();
+                        }
+                        catch (Exception e)
+                        {
+                            // TODO: error code
+                            throw new EMMARuntimeException (e);
+                        }
+                }
+            }
+            else
+            {
+                // copy excluded .class files in full copy mode:
+                copyFile = fullcopymode;
+            }
+        }
+        else
+        {
+            // copy non-.class files in full copy mode:
+            copyFile = fullcopymode;
+        }
+        
+        if (copyFile)
+        {
+            try
+            {
+                final File inFile = Files.newFile (pathDir, file.getPath ());
+                readFile (inFile);
+                
+                final byte [] data = new byte [m_readpos];
+                System.arraycopy (m_readbuf, 0, data, 0, data.length);
+                ++ m_classCopies;
+                
+                final File outFile = getFullOutFile (pathDir, file, IN_CLASSES);
+    
+                addJob (new FileWriteJob (outFile, data, mkdir));
+            }
+            catch (IOException ioe)
+            {
+                // TODO: error code
+                throw new EMMARuntimeException (ioe);
+            }
+        }
+    }
+
+    public final void handleDirEnd (final File pathDir, final File dir)
+    {
+        final Logger log = m_log;
+        if (log.atTRACE2 ()) log.trace2 ("handleDirEnd", "[" + pathDir + "] [" + dir + "]");
+        
+        // in overwrite mode, flush the job queue before going to the next directory:
+        
+        if (m_outMode == OutMode.OUT_MODE_OVERWRITE)
+        {
+            try
+            {
+                drainJobQueue ();
+            }
+            catch (IOException ioe)
+            {
+                // TODO: error code
+                throw new EMMARuntimeException (ioe);
+            }
+        }
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected void reset ()
+    {
+        m_visitor = null;
+        m_mdata = null;
+        m_readbuf = null;
+        m_baos = null;
+ 
+        for (int j = 0; j < m_jobs.length; ++ j) m_jobs [j] = null;
+               
+        if (CLEANUP_TEMP_ARCHIVE_ON_ERRORS)
+        {
+            if (m_archiveOut != null) 
+                try { m_archiveOut.close (); } catch (Exception ignore) {} // unlock the file descriptor for deletion
+            
+            if (m_tempArchiveFile != null)
+                m_tempArchiveFile.delete ();
+        }
+        
+        m_archiveOut = null;
+        m_origArchiveFile = null;
+        m_tempArchiveFile = null;
+        
+        super.reset ();
+    }
+    
+    protected void _run (final IProperties toolProperties)
+    {
+        final Logger log = m_log;
+
+        final boolean verbose = log.atVERBOSE ();
+        if (verbose)
+        {
+            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
+            
+            // [assertion: m_instrPath != null]
+            log.verbose ("instrumentation path:");
+            log.verbose ("{");
+            for (int p = 0; p < m_instrPath.length; ++ p)
+            {
+                final File f = m_instrPath [p];
+                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
+                
+                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
+            }
+            log.verbose ("}");
+            
+            // [assertion: m_outMode != null]
+            log.verbose ("instrumentation output mode: " + m_outMode);
+        }
+        else
+        {
+            log.info ("processing instrumentation path ...");
+        }
+        
+        RuntimeException failure = null;
+        try
+        {
+            long start = System.currentTimeMillis ();
+            m_timeStamp = start;
+            
+            // construct instr path enumerator [throws on illegal input only]:
+            final IPathEnumerator enumerator = IPathEnumerator.Factory.create (m_instrPath, m_canonical, this);
+            
+            // create out dir(s):
+            {
+                if (m_outMode != OutMode.OUT_MODE_OVERWRITE) createDir (m_outDir, true);
+                
+                if ((m_outMode == OutMode.OUT_MODE_FULLCOPY))
+                {
+                    final File classesDir = Files.newFile (m_outDir, CLASSES);
+                    createDir (classesDir, false); // note: not using mkdirs() here
+                    
+                    final File libDir = Files.newFile (m_outDir, LIB);
+                    createDir (libDir, false); // note: not using mkdirs() here
+                }
+            }
+            
+            // get the data out settings [note: this is not conditioned on m_dumpRawData]:
+            File mdataOutFile = m_mdataOutFile;
+            Boolean mdataOutMerge = m_mdataOutMerge;
+            {
+                if (mdataOutFile == null)
+                    mdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_META_DATA_OUT_FILE,
+                                                                         EMMAProperties.DEFAULT_META_DATA_OUT_FILE));
+                
+                if (mdataOutMerge == null)
+                {
+                    final String _dataOutMerge = toolProperties.getProperty (EMMAProperties.PROPERTY_META_DATA_OUT_MERGE,
+                                                                             EMMAProperties.DEFAULT_META_DATA_OUT_MERGE.toString ());
+                    mdataOutMerge = Property.toBoolean (_dataOutMerge) ? Boolean.TRUE : Boolean.FALSE;
+                } 
+            }
+            
+            if (verbose)
+            {
+                log.verbose ("metadata output file: " + mdataOutFile.getAbsolutePath ());
+                log.verbose ("metadata output merge mode: " + mdataOutMerge);
+            }
+                        
+            // TODO: can also register an exit hook to clean up temp files, but this is low value
+            
+            // allocate I/O buffers:
+            m_readbuf = new byte [BUF_SIZE]; // don't reuse this across run() calls to reset it to the original size
+            m_readpos = 0;
+            m_baos = new ByteArrayOStream (BUF_SIZE); // don't reuse this across run() calls to reset it to the original size
+            
+            // reset job queue position: 
+            m_jobPos = 0;
+            
+            m_currentArchiveTS = Long.MAX_VALUE;
+
+            final CoverageOptions options = CoverageOptionsFactory.create (toolProperties);            
+            m_visitor = new InstrVisitor (options); // TODO: reuse this?
+            
+            m_mdata = DataFactory.newMetaData (options);
+            
+            // actual work is driven by the path enumerator:
+            try
+            {
+                enumerator.enumerate ();
+                drainJobQueue ();
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (INSTR_IO_FAILURE, ioe);
+            }
+                       
+            if (log.atINFO ())
+            {
+                final long end = System.currentTimeMillis ();
+                
+                log.info ("instrumentation path processed in " + (end - start) + " ms");
+                log.info ("[" + m_classInstrs + " class(es) instrumented, " + m_classCopies + " resource(s) copied]");
+            }
+            
+            // persist metadata:
+            try
+            {
+                // TODO: create an empty file earlier to catch any errors sooner? [to avoid scenarios where a user waits throught the entire instr run to find out the file could not be written to]
+                
+                if ($assert.ENABLED) $assert.ASSERT (mdataOutFile != null, "m_metadataOutFile is null");
+                
+                if (verbose)
+                {
+                    if (m_mdata != null)
+                    {
+                        log.verbose ("metadata contains " + m_mdata.size () + " entries");
+                    }
+                }
+                
+                if (m_mdata.isEmpty ())
+                {
+                    log.info ("no output created: metadata is empty");
+                }
+                else
+                {
+                    start = System.currentTimeMillis ();
+                    DataFactory.persist (m_mdata, mdataOutFile, mdataOutMerge.booleanValue ());
+                    final long end = System.currentTimeMillis ();
+                    
+                    if (log.atINFO ())
+                    {
+                        log.info ("metadata " + (mdataOutMerge.booleanValue () ? "merged into" : "written to") + " [" + mdataOutFile.getAbsolutePath () + "] {in " + (end - start) + " ms}");
+                    }
+                }
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (OUT_IO_FAILURE, new Object [] {mdataOutFile.getAbsolutePath ()}, ioe);
+            }
+        }
+        catch (SecurityException se)
+        {
+            failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
+        }
+        catch (RuntimeException re)
+        {
+            failure = re;
+        }
+        finally
+        {
+            reset ();
+        }
+        
+        if (failure != null)
+        {
+            if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES))
+            {
+                throw new EMMARuntimeException (UNEXPECTED_FAILURE,
+                                                new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK},
+                                                failure);
+            }
+            else
+                throw failure;
+        }
+    }
+
+    // package: ...............................................................
+    
+    
+    InstrProcessorST ()
+    {
+        m_jobs = new Job [JOB_QUEUE_SIZE];
+        m_instrResult = new InstrVisitor.InstrResult ();
+    }
+    
+    
+    static void writeFile (final byte [] data, final File outFile, final boolean mkdirs)
+        throws IOException
+    {
+        RandomAccessFile raf = null;
+        try
+        {
+            if (mkdirs)
+            {
+                final File parent = outFile.getParentFile ();
+                if (parent != null) parent.mkdirs (); // no error checking here [errors will be throw below]
+            }
+            
+            raf = new RandomAccessFile (outFile, "rw");
+            if (DO_RAF_EXTENSION) raf.setLength (data.length);
+            
+            raf.write (data);
+        } 
+        finally
+        {
+            if (raf != null) raf.close (); // note: intentionally letting the exception percolate up
+        }
+    }
+    
+    static void writeZipEntry (final byte [] data, final ZipOutputStream out, final ZipEntry entry, final boolean isCopy)
+        throws IOException
+    {
+        if (isCopy)
+        {
+            out.putNextEntry (entry); // reusing ' entry' is ok here because we are not changing the data 
+            try
+            {
+                out.write (data);
+            }
+            finally
+            {
+                out.closeEntry ();
+            }
+        }
+        else
+        {
+            // need to compute the checksum which slows things down quite a bit:
+            
+            final ZipEntry entryCopy = new ZipEntry (entry.getName ());
+            entryCopy.setTime (entry.getTime ()); // avoid repeated calls to System.currentTimeMillis() inside the zip stream
+            entryCopy.setMethod (ZipOutputStream.STORED);
+            // [directory status is implicit in the name]
+            entryCopy.setSize (data.length);
+            entryCopy.setCompressedSize (data.length);
+            
+            final CRC32 crc = new CRC32 ();
+            crc.update (data);
+            entryCopy.setCrc (crc.getValue ());
+            
+            out.putNextEntry (entryCopy);
+            try
+            {
+                out.write (data);
+            }
+            finally
+            {
+                out.closeEntry ();
+            }
+        }
+    }
+            
+    // private: ...............................................................
+    
+    
+    private static abstract class Job
+    {
+        protected abstract void run () throws IOException;
+        
+    } // end of nested class
+    
+    
+    private static final class FileWriteJob extends Job
+    {
+        protected void run () throws IOException
+        {
+            writeFile (m_data, m_outFile, m_mkdirs);
+            m_data = null;
+        }
+        
+        FileWriteJob (final File outFile, final byte [] data, final boolean mkdirs)
+        {
+            m_outFile = outFile;
+            m_data = data;
+            m_mkdirs = mkdirs;
+        }
+        
+
+        final File m_outFile;
+        final boolean m_mkdirs;
+        byte [] m_data;
+        
+    } // end of nested class
+    
+
+    private static final class EntryWriteJob extends Job
+    {
+        protected void run () throws IOException
+        {
+            writeZipEntry (m_data, m_out, m_entry, m_isCopy);
+            m_data = null;
+        }
+        
+        EntryWriteJob (final ZipOutputStream out, final byte [] data, final ZipEntry entry, final boolean isCopy)
+        {
+            m_out = out;
+            m_data = data;
+            m_entry = entry;
+            m_isCopy = isCopy;
+        }
+        
+
+        final ZipOutputStream m_out;
+        byte [] m_data;
+        final ZipEntry m_entry;
+        final boolean m_isCopy;
+        
+    } // end of nested class
+
+    
+    private void addJob (final Job job)
+        throws FileNotFoundException, IOException
+    {
+        if (m_jobPos == JOB_QUEUE_SIZE) drainJobQueue ();
+        
+        m_jobs [m_jobPos ++] = job;
+    }
+    
+    private void drainJobQueue ()
+        throws IOException
+    {
+        for (int j = 0; j < m_jobPos; ++ j)
+        {
+            final Job job = m_jobs [j];
+            if (job != null) // a guard just in case
+            {
+                m_jobs [j] = null;
+                job.run ();
+            }
+        }
+        
+        m_jobPos = 0;
+    }
+        
+    /*
+     * Reads into m_readbuf (m_readpos is updated correspondingly)
+     */
+    private void readFile (final File file)
+        throws IOException
+    {
+        final int length = (int) file.length ();
+        
+        ensureReadCapacity (length);
+        
+        InputStream in = null;
+        try
+        {
+            in = new FileInputStream (file);
+            
+            int totalread = 0;
+            for (int read;
+                 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0;
+                 totalread += read);
+            m_readpos = totalread;
+        } 
+        finally
+        {
+            if (in != null) try { in.close (); } catch (Exception ignore) {} 
+        }
+    }
+    
+    /*
+     * Reads into m_readbuf (m_readpos is updated correspondingly)
+     */
+    private void readZipEntry (final ZipInputStream in, final ZipEntry entry)
+        throws IOException
+    {
+        final int length = (int) entry.getSize (); // can be -1 if unknown
+        
+        if (length >= 0)
+        {
+            ensureReadCapacity (length);
+            
+            int totalread = 0;
+            for (int read;
+                 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0;
+                 totalread += read);
+            m_readpos = totalread;
+        }
+        else
+        {
+            ensureReadCapacity (BUF_SIZE);
+            
+            m_baos.reset ();
+            for (int read; (read = in.read (m_readbuf)) >= 0; m_baos.write (m_readbuf, 0, read));
+            
+            m_readbuf = m_baos.copyByteArray ();
+            m_readpos = m_readbuf.length;
+        }
+    }   
+ 
+    private void ensureReadCapacity (final int capacity)
+    {
+        if (m_readbuf.length < capacity)
+        {
+            final int readbuflen = m_readbuf.length;
+            m_readbuf = null;
+            m_readbuf = new byte [Math.max (readbuflen << 1, capacity)];
+        }
+    }
+    
+    
+    // internal run()-scoped state:
+    
+    private final Job [] m_jobs;
+    private final InstrVisitor.InstrResult m_instrResult;
+    
+    private InstrVisitor m_visitor;
+    private IMetaData m_mdata;
+    private byte [] m_readbuf;
+    private int m_readpos;
+    private ByteArrayOStream m_baos; // TODO: code to guard this from becoming too large
+    private int m_jobPos;
+    private long m_currentArchiveTS;
+    private File m_origArchiveFile, m_tempArchiveFile;
+    private JarOutputStream m_archiveOut;
+    private long m_timeStamp;
+    
+    
+    private static final int BUF_SIZE = 32 * 1024;
+    private static final int JOB_QUEUE_SIZE = 128; // a reasonable size chosen empirically after testing a few SCSI/IDE machines
+    private static final boolean CLEANUP_TEMP_ARCHIVE_ON_ERRORS = true;
+    private static final boolean DO_RAF_EXTENSION = true;
+    
+    private static final boolean DO_DEPENDS_CHECKING = true;
+    private static final Class [] EXPECTED_FAILURES; // set in <clinit>
+    
+    static
+    {
+        EXPECTED_FAILURES = new Class []
+        {
+            EMMARuntimeException.class,
+            IllegalArgumentException.class,
+            IllegalStateException.class,
+        };
+    }
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/instr/InstrVisitor.java b/core/java12/com/vladium/emma/instr/InstrVisitor.java
new file mode 100644
index 0000000..cfc64f3
--- /dev/null
+++ b/core/java12/com/vladium/emma/instr/InstrVisitor.java
@@ -0,0 +1,2209 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InstrVisitor.java,v 1.1.1.1.2.4 2004/07/16 23:32:28 vlad_r Exp $
+ */
+package com.vladium.emma.instr;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import com.vladium.jcd.cls.*;
+import com.vladium.jcd.cls.attribute.*;
+import com.vladium.jcd.cls.constant.CONSTANT_Class_info;
+import com.vladium.jcd.cls.constant.CONSTANT_Long_info;
+import com.vladium.jcd.cls.constant.CONSTANT_Methodref_info;
+import com.vladium.jcd.cls.constant.CONSTANT_String_info;
+import com.vladium.jcd.compiler.CodeGen;
+import com.vladium.jcd.lib.Types;
+import com.vladium.jcd.opcodes.IOpcodes;
+import com.vladium.logging.Logger;
+import com.vladium.util.ByteArrayOStream;
+import com.vladium.util.IConstants;
+import com.vladium.util.IntIntMap;
+import com.vladium.util.IntObjectMap;
+import com.vladium.util.IntSet;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.data.ClassDescriptor;
+import com.vladium.emma.data.CoverageOptions;
+import com.vladium.emma.data.IMetadataConstants;
+import com.vladium.emma.data.MethodDescriptor;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class InstrVisitor extends AbstractClassDefVisitor
+                         implements IClassDefVisitor, IAttributeVisitor, IOpcodes, IConstants
+{
+    // public: ................................................................
+    
+    // TODO: m_instrument is unused
+
+    public static final class InstrResult
+    {
+        public boolean m_instrumented;
+        public ClassDescriptor m_descriptor;
+        
+    } // end of nested class    
+    
+    public InstrVisitor (final CoverageOptions options)
+    {
+        m_excludeSyntheticMethods = options.excludeSyntheticMethods ();
+        m_excludeBridgeMethods = options.excludeBridgeMethods ();
+        m_doSUIDCompensation = options.doSUIDCompensation ();
+        
+        m_log = Logger.getLogger ();
+    }
+    
+    /**
+     * Analyzes 'cls' and/or instruments it for coverage:
+     * <ul>
+     *  <li> if 'instrument' is true, the class definition is instrumented for
+     *       coverage if that is feasible
+     *  <li> if 'metadata' is true, the class definition is analysed
+     *       to create a {@link ClassDescriptor} for the original class definition 
+     * </ul>
+     * This method returns null if 'metadata' is 'false' *or* if 'cls' is an
+     * interface [the latter precludes coverage of interface static
+     * initializers and may be removed in the future].<P>
+     * 
+     * NOTE: if 'instrument' is 'true', the caller should always assume that 'cls'
+     * has been mutated by this method even if it returned null. The caller should
+     * then revert to the original class definition that was created as a
+     * <code>cls.clone()</code> or by retaining the original definition bytes.
+     * This part of contract is for efficienty and also simplifies the implementation. 
+     */
+    public void process (final ClassDef cls,
+                         final boolean ignoreAlreadyInstrumented,
+                         final boolean instrument, final boolean metadata,
+                         final InstrResult out)
+    {
+        out.m_instrumented = false;
+        out.m_descriptor = null;
+        
+        if (! (instrument || metadata)) return; // nothing to do
+
+        if (cls.isInterface ())
+            return; // skip interfaces [may change in the future]
+        else
+        {
+            reset ();
+            
+            m_cls = cls;
+            
+            // TODO: handle classes that cannot be instrumented due to bytecode/JVM limitations
+            m_instrument = instrument;
+            m_metadata = metadata;
+            m_ignoreAlreadyInstrumented = ignoreAlreadyInstrumented;
+            
+            // TODO: create 'no instrumentation' execution path here
+            
+            visit ((ClassDef) null, null); // potentially changes m_instrument and m_metadata
+            
+            if (m_metadata)
+            {
+                setClassName (cls.getName ());
+                
+                out.m_descriptor = new ClassDescriptor (m_classPackageName, m_className, m_classSignature, m_classSrcFileName, m_classMethodDescriptors);
+            }
+            
+            out.m_instrumented = m_instrument;
+        }
+    }
+    
+
+    // IClassDefVisitor:
+    
+    public Object visit (final ClassDef ignore, final Object ctx)
+    {
+        final ClassDef cls = m_cls;
+        final String clsVMName = cls.getName ();
+        final String clsName = Types.vmNameToJavaName (clsVMName);
+        
+        final boolean trace1 = m_log.atTRACE1 (); 
+        if (trace1) m_log.trace1 ("visit", "class: [" + clsVMName + "]");
+        
+        
+        // skip synthetic classes if enabled:
+        if (SKIP_SYNTHETIC_CLASSES && cls.isSynthetic ())
+        {
+            m_instrument = false;
+            m_metadata = false;
+            
+            if (trace1) m_log.trace1 ("visit", "skipping synthetic class");
+            return ctx;
+        }
+        
+        // TODO: ideally, this check should be done in outer scope somewhere
+        if (! m_warningIssued && clsName.startsWith (IAppConstants.APP_PACKAGE))
+        {
+            m_warningIssued = true;
+            
+            m_log.warning (IAppConstants.APP_NAME + " classes appear to be included on the instrumentation");
+            m_log.warning ("path: this is not a correct way to use " + IAppConstants.APP_NAME);
+        }
+        
+        // field uniqueness check done to detect double instrumentation:
+        {
+            final int [] existing = cls.getFields (COVERAGE_FIELD_NAME);
+            if (existing.length > 0)
+            {
+                m_instrument = false;
+                m_metadata = false;
+                
+                if (m_ignoreAlreadyInstrumented)
+                {
+                    if (trace1) m_log.trace1 ("visit", "skipping instrumented class");
+                    return ctx;
+                }
+                else
+                {
+                    // TODO: use a app coded exception
+                    throw new IllegalStateException ("class [" + clsName + "] appears to be instrumented already");
+                }
+            }
+        }
+        
+        final IConstantCollection constants = cls.getConstants ();
+        
+        SyntheticAttribute_info syntheticMarker = null;
+        
+        // cache the location of "Synthetic" string:
+        {
+            if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+                m_syntheticStringIndex = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_SYNTHETIC, true);
+        }
+                
+        // add a Fieldref for the runtime coverage collector field:
+        {
+            // note: this is a bit premature if the class has no methods that need
+            // instrumentation
+            // TODO: the mutated version is easily discardable; however, this case
+            // needs attention at metadata/report generation level
+            
+            final int coverageFieldOffset;
+            final String fieldDescriptor = "[[Z";
+            
+            // note that post-4019 builds can modify this field outside of <clinit> (although
+            // it can only happen as part of initializing a set of classes); however, it is legal
+            // to declare this field final:
+            
+            final int fieldModifiers = IAccessFlags.ACC_PRIVATE | IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL;
+            
+            // add declared field:
+            if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+            {
+                final IAttributeCollection fieldAttributes = ElementFactory.newAttributeCollection (1);
+
+                syntheticMarker = new SyntheticAttribute_info (m_syntheticStringIndex);
+                fieldAttributes.add (syntheticMarker);
+    
+                coverageFieldOffset = cls.addField (COVERAGE_FIELD_NAME, fieldDescriptor,
+                    fieldModifiers, fieldAttributes);
+            }
+            else
+            {
+                coverageFieldOffset = cls.addField (COVERAGE_FIELD_NAME, fieldDescriptor,
+                    fieldModifiers);
+            }
+            
+            //add fieldref:
+            m_coverageFieldrefIndex = cls.addFieldref (coverageFieldOffset);
+        }
+        
+        // add a Methodref for Runtime.r():
+        {
+            // TODO: compute this without loading Runtime Class?
+            final String classJVMName = "com/vladium/emma/rt/RT";
+            final int class_index = cls.addClassref (classJVMName);
+            
+            // NOTE: keep this descriptor in sync with the actual signature
+            final String methodDescriptor = "([[ZLjava/lang/String;J)V";
+            final int nametype_index = cls.addNameType ("r", methodDescriptor);
+            
+            m_registerMethodrefIndex = constants.add (new CONSTANT_Methodref_info (class_index, nametype_index));
+        }
+        
+        // SF FR 971186: split the init logic into a separate method so it could
+        // be called from regular method headers if necessary: 
+        
+        // add a Methodref for pre-<clinit> method:
+        {
+            // NOTE: keep this descriptor in sync with the actual signature
+            final String methodDescriptor = "()[[Z";
+            final int nametype_index = cls.addNameType (PRECLINIT_METHOD_NAME, methodDescriptor);
+            
+            m_preclinitMethodrefIndex = constants.add (new CONSTANT_Methodref_info (cls.getThisClassIndex (), nametype_index));
+        }
+        
+        // add a CONSTANT_String that corresponds to the class name [in JVM format]:
+        {
+            m_classNameConstantIndex = constants.add (new CONSTANT_String_info (cls.getThisClass ().m_name_index));
+        }
+        
+        // visit method collection:         
+        visit (cls.getMethods (), ctx);
+        
+        // if necessary, do SUID compensation [need to be done after method
+        // visits when it is known whether a <clinit> was added]:
+        if (m_doSUIDCompensation)
+        {
+            // compensation not necessary if the original clsdef already defined <clinit>:
+            boolean compensate = ((m_clinitStatus & IMetadataConstants.METHOD_ADDED) != 0);
+            
+            int existingSUIDFieldCount = 0;
+            
+            if (compensate)
+            {
+                // compensation not necessary if the original clsdef already controlled it via 'serialVersionUID':
+                {
+                    final int [] existing = cls.getFields (SUID_FIELD_NAME);
+                    existingSUIDFieldCount = existing.length;
+                    
+                    if (existingSUIDFieldCount > 0)
+                    {
+                        final IFieldCollection fields = cls.getFields ();
+                        
+                        for (int f = 0; f < existingSUIDFieldCount; ++ f)
+                        {
+                            final Field_info field = fields.get (existing [f]);
+                            if ((field.getAccessFlags () & (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL))
+                                 == (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL))
+                            {
+                                // TODO: should also check for presence of a non-zero initializer
+                                
+                                compensate = false;
+                                break;
+                            }
+                        }
+                    }
+                }
+                
+                // compensation not necessary if we can determine that this class
+                // does not implement java.io.Serializable/Externalizable:
+                
+                if (compensate && (cls.getThisClassIndex () == 0)) // no superclasses [this tool can't traverse inheritance chains]
+                {
+                    boolean serializable = false;
+                    
+                    final IInterfaceCollection interfaces = cls.getInterfaces ();
+                    for (int i = 0, iLimit = interfaces.size (); i < iLimit; ++ i)
+                    {
+                        final CONSTANT_Class_info ifc = (CONSTANT_Class_info) constants.get (interfaces.get (i));
+                        final String ifcName = ifc.getName (cls); 
+                        if (JAVA_IO_SERIALIZABLE_NAME.equals (ifcName) || JAVA_IO_EXTERNALIZABLE_NAME.equals (ifcName))
+                        {
+                            serializable = true;
+                            break;
+                        }
+                    }
+                    
+                    if (! serializable) compensate = false;
+                }
+            }
+            
+            if (compensate)
+            {
+                if (existingSUIDFieldCount > 0)
+                {
+                    // if we get here, the class declares a 'serialVersionUID' field
+                    // that is not both static and final and/or is not initialized
+                    // statically: warn that SUID compensation may not work
+                    
+                    m_log.warning ("class [" + clsName + "] declares a 'serialVersionUID'");
+                    m_log.warning ("field that is not static and final: this is likely an implementation mistake");
+                    m_log.warning ("and can interfere with " + IAppConstants.APP_NAME + "'s SUID compensation");
+                }
+                
+                final String fieldDescriptor = "J";
+                final int fieldModifiers = IAccessFlags.ACC_PRIVATE | IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL;
+                final IAttributeCollection fieldAttributes = ElementFactory.newAttributeCollection (MARK_ADDED_ELEMENTS_SYNTHETIC ? 2 : 1);
+    
+                final int nameIndex = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CONSTANT_VALUE, true);
+                final int valueIndex = constants.add (new CONSTANT_Long_info (cls.computeSUID (true))); // ignore the added <clinit>
+                
+                final ConstantValueAttribute_info initializer = new ConstantValueAttribute_info (nameIndex, valueIndex);
+                fieldAttributes.add (initializer);
+                    
+                if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+                {
+                    if (syntheticMarker == null) syntheticMarker = new SyntheticAttribute_info (m_syntheticStringIndex);
+                    fieldAttributes.add (syntheticMarker);
+                }
+                
+                cls.addField (SUID_FIELD_NAME, fieldDescriptor, fieldModifiers, fieldAttributes);
+            }
+            
+        } // if (m_doSUIDCompensation)
+        
+        // visit class attributes [to get src file name, etc]:
+        visit (cls.getAttributes (), ctx);
+        
+        return ctx;
+    }
+
+    
+    public Object visit (final IMethodCollection methods, final Object ctx)
+    {
+        final ClassDef cls = m_cls;
+        
+        final boolean trace2 = m_log.atTRACE2 ();
+        
+        final int originalMethodCount = methods.size ();
+        final boolean constructMetadata = m_metadata;
+        
+        // create block count map: TODO: is the extra slot really needed?
+        // - create [potentially unused] slot for added <clinit>
+        m_classBlockCounts = new int [originalMethodCount + 1];        
+        
+        if (constructMetadata)
+        {
+            // prepare to collect metadata:
+            m_classBlockMetadata = new int [originalMethodCount + 1] [] []; // same comments as above
+            
+            m_classMethodDescriptors = new MethodDescriptor [originalMethodCount];
+        }
+        
+       
+        // visit each original method:
+        
+        for (int m = 0; m < originalMethodCount; ++ m)
+        {
+            final Method_info method = methods.get (m);
+            m_methodName = method.getName (cls); 
+            if (trace2) m_log.trace2 ("visit", (method.isSynthetic () ? "synthetic " : "") + "method #" + m + ": [" + m_methodName + "]");
+            
+            final boolean isClinit = IClassDefConstants.CLINIT_NAME.equals (m_methodName);
+                        
+            // TODO: research whether synthetic methods add nontrivially to line coverage or not
+            
+            boolean excluded = false;
+            
+            if (! isClinit)
+            {
+                if (m_excludeSyntheticMethods && method.isSynthetic ())
+                {
+                    excluded = true;
+                    if (trace2) m_log.trace2 ("visit", "skipped synthetic method");
+                }
+                else if (m_excludeBridgeMethods && method.isBridge ())
+                {
+                    excluded = true;
+                    if (trace2) m_log.trace2 ("visit", "skipped bridge method");
+                } 
+            }
+            
+            if (excluded)
+            {
+                if (constructMetadata)
+                {
+                    m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), IMetadataConstants.METHOD_EXCLUDED, m_methodBlockSizes, null, 0);
+                }
+            }
+            else
+            {
+                if ((method.getAccessFlags () & (IAccessFlags.ACC_ABSTRACT | IAccessFlags.ACC_NATIVE)) != 0)
+                {
+                    if (constructMetadata)
+                    {
+                        m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), IMetadataConstants.METHOD_ABSTRACT_OR_NATIVE, m_methodBlockSizes, null, 0);
+                    }
+                    
+                    if (trace2) m_log.trace2 ("visit", "skipped " + (method.isAbstract () ? "abstract" : "native") + " method");
+                }
+                else // this is a regular, non-<clinit> method that has bytecode:
+                {
+                    // reset first line:
+                    m_methodFirstLine = 0;
+                    
+                    // set current method ID:
+                    m_methodID = m;
+                    
+                    if (isClinit)
+                    {
+                        // if <clinit> found: note the ID but delay processing until the very end
+                        m_clinitID = m;
+                        if (trace2) m_log.trace2 ("visit", "<clinit> method delayed");
+                    }
+                    else
+                    {
+                        // visit attributes [skip visit (IAttributeCollection) method]:    
+                        final IAttributeCollection attributes = method.getAttributes ();
+                        final int attributeCount = attributes.size ();
+                        for (int a = 0; a < attributeCount; ++ a)
+                        {
+                            final Attribute_info attribute = attributes.get (a);
+                            attribute.accept (this, ctx);
+                        }
+                        
+                        if (constructMetadata)
+                        {
+                            if ($assert.ENABLED) $assert.ASSERT (m_classBlockCounts [m_methodID] > 0, "invalid block count for method " + m_methodID + ": " + m_classBlockCounts [m_methodID]);
+                            if ($assert.ENABLED) $assert.ASSERT (m_methodBlockSizes != null && m_methodBlockSizes.length == m_classBlockCounts [m_methodID], "invalid block sizes map for method " + m_methodID);
+                            
+                            final int [][] methodBlockMetadata = m_classBlockMetadata [m_methodID];
+                            final int status = (methodBlockMetadata == null ? IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE : 0);
+                            
+                            m_classMethodDescriptors [m] = new MethodDescriptor (m_methodName, method.getDescriptor (cls), status, m_methodBlockSizes, methodBlockMetadata, m_methodFirstLine);
+                        }
+                    }                
+                }
+            }
+        }
+        
+        // add <clinit> (and instrument if needed) [a <clinit> is always needed
+        // even if there are no other instrumented method to act as a load hook]:
+        
+        final boolean instrumentClinit = false; // TODO: make use of this [to limit instrumentation to clinitHeader only], take into account whether we added and whether it is synthetic
+        final Method_info clinit;
+        
+        if (m_clinitID >= 0)
+        {
+            // <clinit> existed in the original class: needs to be covered
+            
+            // m_clinitStatus = 0;
+            clinit = methods.get (m_clinitID);
+            
+            m_classInstrMethodCount = originalMethodCount;
+        }
+        else
+        {
+            // there is no <clinit> defined by the original class: add one [and mark it synthetic]
+            
+            m_clinitStatus = IMetadataConstants.METHOD_ADDED;  // mark as added by us
+            
+            final int attribute_name_index = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true);
+            final int name_index = cls.addCONSTANT_Utf8 (IClassDefConstants.CLINIT_NAME, true);
+            final int descriptor_index = cls.addCONSTANT_Utf8 ("()V", true);
+            
+            final IAttributeCollection attributes;
+            
+            if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+                attributes = ElementFactory.newAttributeCollection (2);
+            else
+                attributes = ElementFactory.newAttributeCollection (1);
+            
+            final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index,
+                0, 0,
+                new byte [] {(byte) _return},
+                AttributeElementFactory.newExceptionHandlerTable (0),
+                ElementFactory.newAttributeCollection (0));
+                
+            attributes.add (code);
+            
+            if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+            {
+                attributes.add (new SyntheticAttribute_info (m_syntheticStringIndex));
+            }
+            
+            clinit = new Method_info (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_PRIVATE, name_index, descriptor_index, attributes);
+            
+            m_clinitID = cls.addMethod (clinit);
+            
+            if (trace2) m_log.trace2 ("visit", "added synthetic <clinit> method");
+            
+            // TODO: this should exclude <clinit> if it were added by us
+            m_classInstrMethodCount = originalMethodCount + 1;
+        }
+        
+        if ($assert.ENABLED) $assert.ASSERT (m_classInstrMethodCount >= 0,
+            "m_classInstrMethodCount not set");
+        
+
+        // visit <clinit>:
+        {
+            m_methodFirstLine = 0;
+            m_methodID = m_clinitID;
+            
+            if (trace2) m_log.trace2 ("visit", (clinit.isSynthetic () ? "synthetic " : "") + "method #" + m_methodID + ": [<clinit>]");
+            
+            final IAttributeCollection attributes = clinit.getAttributes ();
+            final int attributeCount = attributes.size ();
+            for (int a = 0; a < attributeCount; ++ a)
+            {
+                final Attribute_info attribute = attributes.get (a);
+                attribute.accept (this, ctx);
+            }
+        }
+
+        // add pre-<clinit> method:
+        
+        {
+            final int attribute_name_index = cls.addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true);
+            final int name_index = cls.addCONSTANT_Utf8 (PRECLINIT_METHOD_NAME, false);
+            final int descriptor_index = cls.addCONSTANT_Utf8 ("()[[Z", false);
+            
+            final IAttributeCollection attributes;
+            
+            if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+                attributes = ElementFactory.newAttributeCollection (2);
+            else
+                attributes = ElementFactory.newAttributeCollection (1);
+            
+            final ByteArrayOStream buf = new ByteArrayOStream (PRECLINIT_INIT_CAPACITY);  
+            {
+                final int [] blockCounts = m_classBlockCounts;
+                final int instrMethodCount = m_classInstrMethodCount; // actual number of methods to instrument may be less than the size of the block map 
+
+                if ($assert.ENABLED) $assert.ASSERT (blockCounts != null && blockCounts.length >= instrMethodCount,
+                    "invalid block count map");
+                
+                // new and set COVERAGE_FIELD:
+                
+                // push first dimension:
+                CodeGen.push_int_value (buf, cls, instrMethodCount);
+                
+                // [stack +1]
+                
+                // new boolean [][]:
+                final int type_index = cls.addClassref ("[[Z");
+                buf.write4 (_multianewarray,
+                            type_index >>> 8,    // indexbyte1
+                            type_index,          // indexbyte2
+                            1); // only one dimension created here
+                
+                // [stack +1]
+                
+                // clone array ref:
+                buf.write4 (_dup,
+                
+                // [stack +2]
+                
+                // store in the static field
+                            _putstatic,
+                            m_coverageFieldrefIndex >>> 8,    // indexbyte1
+                            m_coverageFieldrefIndex);          // indexbyte2
+                
+                // [stack +1]
+                
+                for (int m = 0; m < instrMethodCount; ++ m)
+                {
+                    final int blockCount = blockCounts [m]; 
+                    if (blockCount > 0)
+                    {
+                        // clone array ref:
+                        buf.write (_dup);
+                        
+                        // [stack +2]
+                        
+                        // push outer dim index:
+                        CodeGen.push_int_value (buf, cls, m);
+                        
+                        // [stack +3]
+                        
+                        // push dim:
+                        CodeGen.push_int_value (buf, cls, blockCount);
+                        
+                        // [stack +4]
+                        
+                        // newarray boolean []:
+                        buf.write3 (_newarray,
+                                    4, // "T_BOOLEAN"
+                        
+                        // add subarray to the outer array:
+                                    _aastore);
+                        
+                        // [stack +1]
+                    }
+                }
+                
+                // [stack +1]
+                
+                {
+                    // clone array ref
+                    buf.write (_dup);
+                    
+                    // [stack +2]
+                    
+                    CodeGen.push_constant_index (buf, m_classNameConstantIndex);
+                    
+                    // [stack +3]
+
+                    buf.write3 (_ldc2_w,
+                                m_stampIndex >>> 8,    // indexbyte1
+                                m_stampIndex);         // indexbyte2
+                    
+                    // [stack +5]
+                    
+                    buf.write3 (_invokestatic,
+                                m_registerMethodrefIndex >>> 8,    // indexbyte1
+                                m_registerMethodrefIndex);         // indexbyte2
+                    
+                    // [stack +1]
+                }
+                
+                // pop and return extra array ref:
+                buf.write (_areturn);
+                
+                // [stack +0]
+            }
+
+            final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index,
+                5, 0, // adjust constants if the bytecode emitted above changes
+                EMPTY_BYTE_ARRAY,
+                AttributeElementFactory.newExceptionHandlerTable (0),
+                ElementFactory.newAttributeCollection (0));
+            
+            code.setCode (buf.getByteArray (), buf.size ());
+                
+            attributes.add (code);
+            
+            if (MARK_ADDED_ELEMENTS_SYNTHETIC)
+            {
+                attributes.add (new SyntheticAttribute_info (m_syntheticStringIndex));
+            }
+            
+            final Method_info preclinit = new Method_info (IAccessFlags.ACC_STATIC | IAccessFlags.ACC_PRIVATE, name_index, descriptor_index, attributes);
+            cls.addMethod (preclinit);
+            
+            if (trace2) m_log.trace2 ("visit", "added synthetic pre-<clinit> method");
+        }
+
+        
+        if (constructMetadata)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (m_classBlockCounts [m_methodID] > 0, "invalid block count for method " + m_methodID + " (" + IClassDefConstants.CLINIT_NAME + "): " + m_classBlockCounts [m_methodID]);
+            if ($assert.ENABLED) $assert.ASSERT (m_methodBlockSizes != null && m_methodBlockSizes.length == m_classBlockCounts [m_methodID], "invalid block sizes map for method " + m_methodID);
+            
+            final int [][] methodBlockMetadata = m_classBlockMetadata [m_methodID];
+            m_clinitStatus |= (methodBlockMetadata == null ? IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE : 0);
+            
+            // TODO: this still does not process not added/synthetic case  
+            
+            if ((m_clinitStatus & IMetadataConstants.METHOD_ADDED) == 0)
+                m_classMethodDescriptors [m_methodID] = new MethodDescriptor (IClassDefConstants.CLINIT_NAME, clinit.getDescriptor (cls), m_clinitStatus, m_methodBlockSizes, methodBlockMetadata, m_methodFirstLine);
+        }
+        
+        return ctx;
+    }
+
+
+    public Object visit (final IAttributeCollection attributes, Object ctx)
+    {
+        for (int a = 0, aCount = attributes.size (); a < aCount; ++ a)
+        {
+            // TODO: define a global way to set the mask set of attrs to be visited
+            attributes.get (a).accept (this, ctx);
+        } 
+
+        return ctx;
+    }
+    
+    
+    // IAttributeVisitor:
+
+    public Object visit (final CodeAttribute_info attribute, final Object ctx)
+    {
+        final boolean trace2 = m_log.atTRACE2 ();
+        final boolean trace3 = m_log.atTRACE3 ();
+        
+        final byte [] code = attribute.getCode ();
+        final int codeSize = attribute.getCodeSize ();
+        
+        if (trace2) m_log.trace2 ("visit", "code attribute for method #" + m_methodID + ": size = " + codeSize);
+        
+        final IntSet leaders = new IntSet ();
+        
+        // instructionMap.get(ip) is the number of instructions in code[0-ip)
+        // [this map will include a mapping for code length as well]
+        final IntIntMap /* int(ip)->instr count */ instructionMap = new IntIntMap ();
+        
+        // add first instruction and all exc handler start pcs:
+        leaders.add (0); 
+        
+        final IExceptionHandlerTable exceptions = attribute.getExceptionTable ();
+        final int exceptionCount = exceptions.size ();
+        for (int e = 0; e < exceptionCount; ++ e)
+        {
+            final Exception_info exception = exceptions.get (e);
+            leaders.add (exception.m_handler_pc);
+        }
+        
+        
+        final IntObjectMap branches = new IntObjectMap ();
+        
+        // determine block leaders [an O(code length) loop]:
+        
+        boolean branch = false;
+        boolean wide = false;
+
+        int instructionCount = 0;
+        instructionMap.put (0, 0);
+        
+        for (int ip = 0; ip < codeSize; )
+        {
+            final int opcode = 0xFF & code [ip];
+            int size = 0; // will be set to -<real size> for special cases in the switch below 
+            
+            //if (trace3) m_log.trace3 ("parse", MNEMONICS [opcode]);
+            // "visitor.visit (opcode, wide, ip, null)":
+            
+            { // "opcode visit" logic:
+                
+                int iv, ov;
+                
+                if (branch)
+                {
+                    // previous instruction was a branch: this one is a leader
+                    leaders.add (ip);
+                    branch = false;
+                }
+                
+                switch (opcode)
+                {
+                    case _ifeq:
+                    case _iflt:
+                    case _ifle:
+                    case _ifne:
+                    case _ifgt:
+                    case _ifge:
+                    case _ifnull:
+                    case _ifnonnull:
+                    case _if_icmpeq:
+                    case _if_icmpne:
+                    case _if_icmplt:
+                    case _if_icmpgt:
+                    case _if_icmple:
+                    case _if_icmpge:
+                    case _if_acmpeq:
+                    case _if_acmpne:
+                    {
+                        //ov = getI2 (code, ip + 1);
+                        int scan = ip + 1;
+                        ov = (code [scan] << 8) | (0xFF & code [++ scan]);
+                        
+                        final int target = ip + ov;
+                        leaders.add (target); 
+                        
+                        branches.put (ip, new IFJUMP2 (opcode, target));
+                        branch = true;
+                    }
+                    break;
+
+
+                    case _goto:
+                    case _jsr:
+                    {
+                        //ov = getI2 (code, ip + 1);
+                        int scan = ip + 1;
+                        ov = (code [scan] << 8) | (0xFF & code [++ scan]);
+                        
+                        final int target = ip + ov;
+                        leaders.add (target); 
+                        
+                        branches.put (ip, new JUMP2 (opcode, target));
+                        branch = true;
+                    }
+                    break;
+
+
+                    case _lookupswitch:
+                    {
+                        int scan = ip + 4 - (ip & 3); // eat padding
+                        
+                        ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                        leaders.add (ip + ov);
+                        
+                        //final int npairs = getU4 (code, scan);
+                        //scan += 4;
+                        final int npairs = ((0xFF & code [++ scan]) << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                        
+                        final int [] keys = new int [npairs];
+                        final int [] targets = new int [npairs + 1];
+                        targets [0] = ip + ov;
+                        
+                        for (int p = 0; p < npairs; ++ p)
+                        {
+                            //iv = getI4 (code, scan);
+                            //scan += 4;
+                            iv = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                            keys [p] = iv;
+                            
+                            
+                            //ov = getI4 (code, scan);
+                            //scan += 4;
+                            ov = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                            targets [p + 1] = ip + ov;
+                            leaders.add (ip + ov);
+                        }
+                        
+                        branches.put (ip, new LOOKUPSWITCH (keys, targets));
+                        branch = true;
+                        
+                        size = ip - scan - 1; // special case
+                    }
+                    break;
+
+                    
+                    case _tableswitch:
+                    {
+                        int scan = ip + 4 - (ip & 3); // eat padding
+                        
+                        ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                        leaders.add (ip + ov);
+                                                
+                        //final int low = getI4 (code, scan + 4);
+                        final int low = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                        //final int high = getI4 (code, scan + 8);
+                        //scan += 12;
+                        final int high = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                        
+                        final int [] targets = new int [high - low + 2];
+                        targets [0] = ip + ov;
+                        
+                        for (int index = low; index <= high; ++ index)
+                        {
+                            //ov = getI4 (code, scan);
+                            ov = (code [++ scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                            targets [index - low + 1] = ip + ov;
+                            leaders.add (ip + ov);
+                            //scan += 4;
+                        }
+
+                        branches.put (ip, new TABLESWITCH (low, high, targets));
+                        branch = true;
+                        
+                        size = ip - scan - 1; // special case
+                    }
+                    break;
+                        
+
+                    case _goto_w:
+                    case _jsr_w:
+                    {
+                        int scan = ip + 1;
+                        //ov = getI4 (code, ip + 1);
+                        ov = (code [scan] << 24) | ((0xFF & code [++ scan]) << 16) | ((0xFF & code [++ scan]) << 8) | (0xFF & code [++ scan]);
+                        final int target = ip + ov;
+                        
+                        leaders.add (target);
+                        
+                        branches.put (ip, new JUMP4 (opcode, target));
+                        branch = true;
+                    }
+                    break;
+
+
+                    case _ret:
+                    {
+                        int scan = ip + 1;
+                        iv = wide ? (((0xFF & code [scan]) << 8) | (0xFF & code [++ scan])) : (0xFF & code [scan]);
+                        
+                        branches.put (ip, new RET (opcode, iv));
+                        branch = true;
+                    } 
+                    break; 
+
+
+                    case _athrow:
+                    case _ireturn:
+                    case _lreturn:
+                    case _freturn:
+                    case _dreturn:
+                    case _areturn:
+                    case _return:
+                    {
+                        branches.put (ip, new TERMINATE (opcode));
+                        branch = true;
+                    }
+                    break;
+                    
+                } // end of switch
+                
+            } // end of processing the current opcode
+            
+            
+            // shift to the next instruction [this is the only block that adjusts 'ip']:
+            
+            if (size == 0)
+                size = (wide ? WIDE_SIZE : NARROW_SIZE) [opcode];
+            else
+                size = -size;
+            
+            ip += size;
+            wide = (opcode == _wide);
+            
+            instructionMap.put (ip, ++ instructionCount);
+            
+        } // end of for
+        
+        
+        // split 'code' into an ordered list of basic blocks [O(block count) loops]:
+        
+        final int blockCount = leaders.size ();
+        if (trace2) m_log.trace2 ("visit", "method contains " + blockCount + " basic blocks");
+        
+        final BlockList blocks = new BlockList (blockCount);
+        
+        final int [] _leaders = new int [blockCount + 1]; // room for end-of-code leader at the end 
+        leaders.values (_leaders, 0);
+        _leaders [blockCount] = codeSize;
+        
+        Arrays.sort (_leaders);
+        
+        final int [] _branch_locations = branches.keys (); 
+        Arrays.sort (_branch_locations);
+        
+        final IntIntMap leaderToBlockID = new IntIntMap (_leaders.length);
+        
+        if (m_metadata)
+        {
+            // help construct a MethodDescriptor for the current method:
+            
+            m_methodBlockSizes = new int [blockCount];
+            m_methodBlockOffsets = _leaders;
+        }
+
+        // compute signature even if metadata is not needed (because the instrumented
+        // classdef uses it):
+        consumeSignatureData (m_methodID, _leaders);
+        
+        // pass 1:
+        
+        final int [] intHolder = new int [1];
+        int instr_count = 0, prev_instr_count;
+        
+        for (int bl = 0, br = 0; bl < blockCount; ++ bl)
+        {
+            final Block block = new Block ();
+            blocks.m_blocks.add (block);
+            
+            final int leader = _leaders [bl];
+            
+            block.m_first = leader; // m_first set
+            leaderToBlockID.put (leader, bl);
+            
+            final int next_leader = _leaders [bl + 1];
+            boolean branchDelimited = false;
+            
+            prev_instr_count = instr_count;
+
+            if (_branch_locations.length > br)
+            {
+                final int next_branch_location = _branch_locations [br];
+                if (next_branch_location < next_leader)
+                {
+                    branchDelimited = true;
+                    
+                    block.m_length = next_branch_location - leader; // m_length set
+                    
+                    if ($assert.ENABLED)
+                        $assert.ASSERT (instructionMap.get (next_branch_location, intHolder), "no mapping for " + next_branch_location);
+                    else
+                        instructionMap.get (next_branch_location, intHolder);
+                        
+                    instr_count = intHolder [0] + 1; // [+ 1 for the branch]
+                     
+                    block.m_branch = (Branch) branches.get (next_branch_location);
+                    block.m_branch.m_parentBlockID = bl; // m_branch set
+                    
+                    ++ br;
+                }
+            }
+            
+            if (! branchDelimited)
+            {
+                block.m_length = next_leader - leader; // m_length set
+                
+                if ($assert.ENABLED)
+                    $assert.ASSERT (instructionMap.get (next_leader, intHolder), "no mapping for " + next_leader);
+                else
+                    instructionMap.get (next_leader, intHolder);
+                
+                instr_count = intHolder [0];
+            }
+            
+            block.m_instrCount = instr_count - prev_instr_count; // m_instrCount set
+            
+            if ($assert.ENABLED) $assert.ASSERT (block.m_length == 0 || block.m_instrCount > 0, "invalid instr count for block " + bl + ": " + block.m_instrCount);
+            if (m_metadata) m_methodBlockSizes [bl] = block.m_instrCount; 
+        }
+        
+        // pass 2:
+        
+        final Block [] _blocks = (Block []) blocks.m_blocks.toArray (new Block [blockCount]);
+        
+        for (int l = 0; l < blockCount; ++ l)
+        {
+            final Block block = _blocks [l];
+             
+            if (block.m_branch != null)
+            {
+                final int [] targets = block.m_branch.m_targets;
+                if (targets != null)
+                {
+                    for (int t = 0, targetCount = targets.length; t < targetCount; ++ t)
+                    {
+                        // TODO: HACK ! convert block absolute offsets to block IDs:
+                        
+                        if ($assert.ENABLED)
+                            $assert.ASSERT (leaderToBlockID.get (targets [t], intHolder), "no mapping for " + targets [t]);
+                        else
+                            leaderToBlockID.get (targets [t], intHolder);
+                            
+                        targets [t] = intHolder [0];
+                    }
+                }
+            }
+        }
+
+        
+        // update block count map [used later by <clinit> visit]:
+        m_classBlockCounts [m_methodID] = blockCount;
+        
+        // actual basic block instrumentation:
+        {
+            if (trace2) m_log.trace2 ("visit", "instrumenting... ");
+            
+            // determine the local var index for the var that will alias COVERAGE_FIELD:
+            final int localVarIndex = attribute.m_max_locals ++;
+            
+            if (m_methodID == m_clinitID) // note: m_clinitID can be -1 if <clinit> has not been visited yet
+            {
+                 // add a long stamp constant after all the original methods have been visited:
+             
+                m_stampIndex = m_cls.getConstants ().add (new CONSTANT_Long_info (m_classSignature));
+                
+                blocks.m_header = new clinitHeader (this, localVarIndex);
+            }
+            else
+                blocks.m_header = new methodHeader (this, localVarIndex);
+            
+            int headerMaxStack = blocks.m_header.maxstack ();
+            int methodMaxStack = 0;
+            
+            for (int l = 0; l < blockCount; ++ l)
+            {
+                final Block block = _blocks [l];
+                
+                final CodeSegment insertion = new BlockSegment (this, localVarIndex, l);
+                block.m_insertion = insertion;
+                
+                final int insertionMaxStack = insertion.maxstack (); 
+                if (insertionMaxStack > methodMaxStack)
+                    methodMaxStack = insertionMaxStack;
+            }
+            
+            // update maxstack as needed [it can only grow]:
+            {
+                final int oldMaxStack = attribute.m_max_stack;
+                
+                attribute.m_max_stack += methodMaxStack; // this is not precise, but still need to add because the insertion may be happening at the old maxstack point
+                
+                if (headerMaxStack > attribute.m_max_stack)
+                attribute.m_max_stack = headerMaxStack;
+                
+                if (trace3) m_log.trace3 ("visit", "increasing maxstack by " + (attribute.m_max_stack - oldMaxStack));
+            }
+            
+            if ($assert.ENABLED) $assert.ASSERT (blocks.m_header != null, "header not set");
+        }
+        
+
+        // assemble all blocks into an instrumented code block:
+        if (trace2) m_log.trace2 ("visit", "assembling... ");
+        
+        int newcodeCapacity = codeSize << 1;
+        if (newcodeCapacity < EMIT_CTX_MIN_INIT_CAPACITY) newcodeCapacity = EMIT_CTX_MIN_INIT_CAPACITY;
+
+        final ByteArrayOStream newcode = new ByteArrayOStream (newcodeCapacity); // TODO: empirical capacity
+        final EmitCtx emitctx = new EmitCtx (blocks, newcode);
+        
+        // create a jump adjustment map:
+        final int [] jumpAdjOffsets = new int [blockCount]; // room for initial 0  + (blockCount - 1)
+        final int [] jumpAdjMap = new int [jumpAdjOffsets.length]; // room for initial 0  + (blockCount - 1)
+        
+        if ($assert.ENABLED) $assert.ASSERT (jumpAdjOffsets.length == jumpAdjMap.length,
+            "jumpAdjOffsets and jumpAdjMap length mismatch");
+        
+        // header:
+        blocks.m_header.emit (emitctx);
+        // jumpAdjOffsets [0] = 0: redundant
+        jumpAdjMap [0] = emitctx.m_out.size ();
+        
+        // rest of blocks:
+        for (int l = 0; l < blockCount; ++ l)
+        {
+            final Block block = _blocks [l];
+            
+            if (l + 1 < blockCount)
+            {
+                jumpAdjOffsets [l + 1] = _blocks [l].m_first + _blocks [l].m_length; // implies the insertion goes just before the branch
+            }
+            
+            block.emit (emitctx, code);
+            
+            // TODO: this breaks if code can shrink:
+            if (l + 1 < blockCount)
+            {
+                jumpAdjMap [l + 1] = emitctx.m_out.size () - _blocks [l + 1].m_first;
+            }
+        }
+        
+        m_methodJumpAdjOffsets = jumpAdjOffsets;
+        m_methodJumpAdjValues = jumpAdjMap;
+        
+        if (trace3)
+        {
+            final StringBuffer s = new StringBuffer ("jump adjustment map:" + EOL);
+            for (int a = 0; a < jumpAdjOffsets.length; ++ a)
+            {
+                s.append ("    " + jumpAdjOffsets [a] + ": +" + jumpAdjMap [a]);
+                if (a < jumpAdjOffsets.length - 1) s.append (EOL);
+            }
+            
+            m_log.trace3 ("visit", s.toString ());
+        }
+        
+        final byte [] _newcode = newcode.getByteArray (); // note: not cloned 
+        final int _newcodeSize = newcode.size ();
+         
+        // [all blocks have had their m_first adjusted]
+        
+        // backpatching pass:        
+        if (trace3) m_log.trace3 ("visit", "backpatching " + emitctx.m_backpatchQueue.size () + " ip(s)");
+        
+        for (Iterator i = emitctx.m_backpatchQueue.iterator (); i.hasNext (); )
+        {
+            final int [] patchData = (int []) i.next ();
+            int ip = patchData [1];
+            
+            if ($assert.ENABLED) $assert.ASSERT (patchData != null, "null patch data for ip " + ip);
+            
+            final int jump = _blocks [patchData [3]].m_first - patchData [2];
+            if ($assert.ENABLED) $assert.ASSERT (jump > 0, "negative backpatch jump offset " + jump + " for ip " + ip);
+            
+            switch (patchData [0])
+            {
+                case 4:
+                {
+                    _newcode [ip ++] = (byte) (jump >>> 24);
+                    _newcode [ip ++] = (byte) (jump >>> 16);
+                    
+                } // *FALL THROUGH*
+                
+                case 2:
+                {
+                    _newcode [ip ++] = (byte) (jump >>> 8);
+                    _newcode [ip] = (byte) jump;
+                }
+            }
+        }
+        
+        attribute.setCode (_newcode, _newcodeSize);
+        if (trace2) m_log.trace2 ("visit", "method assembled into " + _newcodeSize + " code bytes");
+
+        
+        // adjust bytecode offsets in the exception table:
+        final IExceptionHandlerTable exceptionTable = attribute.getExceptionTable ();
+        for (int e = 0; e < exceptionTable.size (); ++ e)
+        {
+            final Exception_info exception = exceptionTable.get (e);
+            
+            int adjSegment = lowbound (jumpAdjOffsets, exception.m_start_pc);
+            exception.m_start_pc += jumpAdjMap [adjSegment];
+            
+            adjSegment = lowbound (jumpAdjOffsets, exception.m_end_pc);
+            exception.m_end_pc += jumpAdjMap [adjSegment];
+            
+            adjSegment = lowbound (jumpAdjOffsets, exception.m_handler_pc);
+            exception.m_handler_pc += jumpAdjMap [adjSegment];
+        }
+
+        
+        // visit other nested attributes [LineNumberAttribute, etc]:    
+        final IAttributeCollection attributes = attribute.getAttributes ();
+        final int attributeCount = attributes.size ();
+        for (int a = 0; a < attributeCount; ++ a)
+        {
+            final Attribute_info nested = attributes.get (a);
+            nested.accept (this, ctx);
+        }
+        
+        return ctx;
+    }
+    
+
+    public Object visit (final LineNumberTableAttribute_info attribute, final Object ctx)
+    {
+        final boolean trace2 = m_log.atTRACE2 ();
+        final boolean trace3 = m_log.atTRACE3 (); 
+        if (trace2) m_log.trace2 ("visit", "attribute: [" + attribute.getName (m_cls) + "]");
+        
+        final int lineCount = attribute.size ();
+        
+        if (m_metadata)
+        {
+            if (trace2) m_log.trace2 ("visit", "processing line number table for metadata...");
+            
+            final int blockCount = m_classBlockCounts [m_methodID];
+            if ($assert.ENABLED) $assert.ASSERT (blockCount > 0, "invalid method block count for method " + m_methodID);
+            
+            final int [][] blockLineMap = new int [blockCount][];
+            
+            if ($assert.ENABLED) $assert.ASSERT (blockCount + 1 == m_methodBlockOffsets.length,
+                    "invalid m_methodBlockOffsets");
+            
+            if (lineCount == 0)
+            {
+                for (int bl = 0; bl < blockCount; ++ bl)
+                    blockLineMap [bl] = EMPTY_INT_ARRAY;
+            }
+            else
+            {
+                // TODO: this code does not work if there are multiple LineNumberTableAttribute attributes for the method
+
+                final LineNumber_info [] sortedLines = new LineNumber_info [attribute.size ()];
+                
+                for (int l = 0; l < lineCount; ++ l)
+                {
+                    final LineNumber_info line = attribute.get (l);
+                    sortedLines [l] = line;
+                }
+                
+                Arrays.sort (sortedLines, LINE_NUMBER_COMPARATOR);
+                
+                // construct block->line mapping: TODO: is the loop below the fastest it can be done?
+                
+                final int [] methodBlockOffsets = m_methodBlockOffsets;
+                
+                LineNumber_info line = sortedLines [0]; // never null
+                LineNumber_info prev_line = null;
+                
+                // remember the first line:
+                m_methodFirstLine = line.m_line_number;
+                
+                for (int bl = 0, l = 0; bl < blockCount; ++ bl)
+                {                   
+                    final IntSet blockLines = new IntSet ();
+                    
+                    if ((prev_line != null) && (line.m_start_pc > methodBlockOffsets [bl]))
+                    {
+                        blockLines.add (prev_line.m_line_number);
+                    }
+                    
+                    while (line.m_start_pc < methodBlockOffsets [bl + 1])
+                    {
+                        blockLines.add (line.m_line_number);
+                        
+                        if (l == lineCount - 1)
+                            break;
+                        else
+                        {
+                            prev_line = line;
+                            line = sortedLines [++ l]; // advance to the next line
+                        }
+                    }
+                    
+                    blockLineMap [bl] = blockLines.values ();
+                }                
+            }
+            
+            m_classBlockMetadata [m_methodID] = blockLineMap;
+            
+            if (trace3)
+            {
+                StringBuffer s = new StringBuffer ("block-line map for method #" + m_methodID + ":");
+                for (int bl = 0; bl < blockCount; ++ bl)
+                {
+                    s.append (EOL);
+                    s.append ("    block " + bl + ": ");
+                    
+                    final int [] lines = blockLineMap [bl];
+                    for (int l = 0; l < lines.length; ++ l)
+                    {
+                        if (l != 0) s.append (", ");
+                        s.append (lines [l]);
+                    }
+                }
+                
+                m_log.trace3 ("visit", s.toString ());
+            }
+        }
+        
+        for (int l = 0; l < lineCount; ++ l)
+        {
+            final LineNumber_info line = attribute.get (l);
+            
+            // TODO: make this faster using either table assist or the sorted array in 'sortedLines'
+            
+            // adjust bytecode offset for line number mapping:
+            int adjSegment = lowbound (m_methodJumpAdjOffsets, line.m_start_pc);                
+            line.m_start_pc += m_methodJumpAdjValues [adjSegment];
+        }
+        
+        return ctx;
+    }
+    
+    // TODO: line var table as well
+    
+
+    // no-op visits:
+
+    public Object visit (final ExceptionsAttribute_info attribute, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    public Object visit (final ConstantValueAttribute_info attribute, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    public Object visit (final SourceFileAttribute_info attribute, final Object ctx)
+    {
+        m_classSrcFileName = attribute.getSourceFile (m_cls).m_value;
+
+        return ctx;
+    }
+
+    public Object visit (final SyntheticAttribute_info attribute, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    public Object visit (final BridgeAttribute_info attribute, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    public Object visit (final InnerClassesAttribute_info attribute, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    public Object visit (final GenericAttribute_info attribute, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class BlockList
+    {
+        BlockList ()
+        {
+            m_blocks = new ArrayList ();
+        }
+        
+        BlockList (final int capacity)
+        {
+            m_blocks = new ArrayList (capacity);
+        }
+        
+        final List /* Block */ m_blocks; // TODO: might as well use an array here?
+        CodeSegment m_header;
+        
+    } // end of nested class 
+    
+    
+    private static final class Block
+    {
+        int m_first;    // inclusive offset of the leader instruction [first instr in the block]
+        //int m_last;     // exclusive offset of the last non-branch instruction [excludes possible control transfer at the end]
+        int m_length;   // excluding the branch statement [can be 0]
+        int m_instrCount; // size in instructions, including the [optional] original branch; [m_insertion is not counted] 
+        
+        // NOTE: it is possible that m_first == m_last [the block is empty except for a possible control transfer instr]
+        
+//        public int maxlength ()
+//        {
+//            // TODO: cache
+//            return m_length
+////                + (m_insertion != null ? m_insertion.maxlength () : 0)
+//                + (m_branch != null ? m_branch.maxlength () : 0);
+//        }
+        
+        /**
+         * When this is called, all previous blocks have been written out and 
+         * their m_first have been updated.
+         */
+        void emit (final EmitCtx ctx, final byte [] code) // TODO: move 'code' into 'ctx'
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            final int first = m_first;
+            
+            m_first = out.size (); // update position to be within new code array
+            
+            for (int i = 0, length = m_length; i < length; ++ i)
+            {
+                out.write (code [first + i]);
+            }
+            
+            if (m_insertion != null)
+                m_insertion.emit (ctx);
+            
+            if (m_branch != null)
+                m_branch.emit (ctx);
+        }
+        
+        public CodeSegment m_insertion;
+        public Branch m_branch; // falling through is implied by this being null
+        
+    } // end of nested class
+    
+    
+    static final class EmitCtx
+    {
+        // TODO: profile to check that ByteArrayOStream.write() is not the bottleneck
+        
+        EmitCtx (final BlockList blocks, final ByteArrayOStream out)
+        {
+            m_blocks = blocks;
+            m_out = out;
+            
+            m_backpatchQueue = new ArrayList ();
+        }
+        
+        final BlockList m_blocks;
+        final ByteArrayOStream m_out;
+        final List /* int[4] */ m_backpatchQueue;
+        
+    } // end of nested class
+    
+    
+    /**
+     * A Branch does not add any maxlocals/maxstack requirements.
+     */
+    static abstract class Branch
+    {
+        protected Branch (final int opcode, final int [] targets)
+        {
+            m_opcode = (byte) opcode;
+            m_targets = targets;
+        }
+        
+        /*
+         * Called when targets are block IDs, before emitting. 
+         */
+        int maxlength () { return 1; }
+        
+        abstract void emit (EmitCtx ctx);
+        
+        // TODO: this method must signal when it is necessary to switch to long jump form
+        protected final void emitJumpOffset2 (final EmitCtx ctx, final int ip, final int targetBlockID)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            
+            if (targetBlockID <= m_parentBlockID)
+            {
+                // backwards branch:
+                final int jumpOffset = ((Block) ctx.m_blocks.m_blocks.get (targetBlockID)).m_first - ip;
+                
+                out.write2 (jumpOffset >>> 8,   // targetbyte1
+                            jumpOffset);         // targetbyte2
+            }
+            else
+            {
+                final int jumpOffsetLocation = out.size (); 
+                
+                // else write out zeros and submit for backpatching:
+                out.write2 (0,
+                            0);
+                
+                ctx.m_backpatchQueue.add (new int [] {2, jumpOffsetLocation, ip, targetBlockID});
+            }
+        }
+        
+        protected final void emitJumpOffset4 (final EmitCtx ctx, final int ip, final int targetBlockID)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            
+            if (targetBlockID <= m_parentBlockID)
+            {
+                // backwards branch:
+                final int jumpOffset = ((Block) ctx.m_blocks.m_blocks.get (targetBlockID)).m_first - ip;
+                
+                out.write4 (jumpOffset >>> 24,    // targetbyte1
+                            jumpOffset >>> 16,    // targetbyte2
+                            jumpOffset >>> 8,     // targetbyte3
+                            jumpOffset);           // targetbyte4
+            }
+            else
+            {
+                final int jumpOffsetLocation = out.size (); 
+                
+                // else write out zeros and submit for backpatching:
+                out.write4 (0,
+                            0,
+                            0,
+                            0);
+                
+                ctx.m_backpatchQueue.add (new int [] {4, jumpOffsetLocation, ip, targetBlockID});
+            }
+        } 
+        
+        final byte m_opcode;
+        final int [] m_targets; // could be code offsets or block IDs
+        
+        int m_parentBlockID;
+        
+    } // end of nested class    
+    
+    
+    // TODO: these could be static instance-pooled
+    static final class TERMINATE extends Branch // _[x]return, _athrow
+    {
+        TERMINATE (final int opcode)
+        {
+            super (opcode, null);
+        }      
+        
+        int length () { return 1; }
+        
+        void emit (final EmitCtx ctx)
+        {
+            ctx.m_out.write (m_opcode);
+        }
+        
+    } // end of nested class
+    
+    
+    static final class RET extends Branch // [wide] ret
+    {
+        RET (final int opcode, final int varindex)
+        {
+            super (opcode, null);
+            m_varindex = varindex;
+        }      
+        
+        int length () { return (m_varindex <= 0xFF) ? 2 : 3; }
+        
+        void emit (final EmitCtx ctx)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            
+            if (m_varindex <= 0xFF)
+            {
+                out.write2 (m_opcode,
+                            m_varindex);  // indexbyte
+            }
+            else
+            {
+                out.write4 (_wide,
+                            m_opcode,
+                            m_varindex >>> 8,   // indexbyte1
+                            m_varindex);         // indexbyte2
+            }
+        }
+        
+        final int m_varindex;
+        
+    } // end of nested class
+    
+    
+    static final class JUMP2 extends Branch // _goto, _jsr
+    {
+        JUMP2 (final int opcode, final int target)
+        {
+            super (opcode, new int [] {target});
+        }
+        
+        int maxlength () { return 5; }
+        
+        void emit (final EmitCtx ctx)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            final int targetBlockID = m_targets [0];
+            final int ip = out.size ();
+            
+            // TODO: switch to 4-byte long form if jump > 32k
+            
+            out.write (m_opcode);
+            emitJumpOffset2 (ctx, ip, targetBlockID);
+        }
+        
+    } // end of nested class
+    
+    
+    static final class JUMP4 extends Branch // _goto_w, _jsr_w
+    {
+        JUMP4 (final int opcode, final int target)
+        {
+            super (opcode, new int [] {target});
+        }
+        
+        int maxlength () { return 5; }
+        
+        void emit (final EmitCtx ctx)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            final int targetBlockID = m_targets [0];
+            final int ip = out.size ();
+            
+            out.write (m_opcode);
+            emitJumpOffset4 (ctx, ip, targetBlockID);
+        }
+        
+    } // end of nested class
+    
+    
+    static final class IFJUMP2 extends Branch // _ifxxx
+    {
+        IFJUMP2 (final int opcode, final int target)
+        {
+            super (opcode, new int [] {target});
+        }
+        
+        int maxlength () { return 8; }
+        
+        void emit (final EmitCtx ctx)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            final int targetBlockID = m_targets [0];
+            final int ip = out.size ();
+            
+            // TODO: switch to 8-byte long form if jump > 32k
+            
+            out.write (m_opcode);
+            emitJumpOffset2 (ctx, ip, targetBlockID);
+        }
+        
+    } // end of nested class
+
+    
+    static final class LOOKUPSWITCH extends Branch
+    {
+        LOOKUPSWITCH (final int [] keys, final int [] targets /* first one is default */)
+        {
+            super (_lookupswitch, targets);
+            m_keys = keys;
+        }
+        
+        int maxlength () { return 12 + (m_keys.length << 3); }
+        
+        void emit (final EmitCtx ctx)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            final int ip = out.size ();
+            
+            out.write (m_opcode);
+            
+            // padding bytes:
+            for (int p = 0, padCount = 3 - (ip & 3); p < padCount; ++ p) out.write (0);
+             
+            // default:
+            emitJumpOffset4 (ctx, ip, m_targets [0]);
+            
+            // npairs count:
+            final int npairs = m_keys.length;
+            out.write4 (npairs >>> 24,  // byte1
+                        npairs >>> 16,  // byte2
+                        npairs >>> 8,   // byte3
+                        npairs);        // byte4
+            
+            // keyed targets:
+            for (int t = 1; t < m_targets.length; ++ t)
+            {
+                final int key = m_keys [t - 1];
+                out.write4 (key >>> 24,  // byte1
+                            key >>> 16,  // byte2
+                            key >>> 8,   // byte3
+                            key);         // byte4
+
+                // key target:
+                emitJumpOffset4 (ctx, ip, m_targets [t]); 
+            }
+        }
+        
+        final int [] m_keys;
+        
+    } // end of nested class
+
+
+    static final class TABLESWITCH extends Branch
+    {
+        TABLESWITCH (final int low, final int high, final int [] targets /* first one is default */)
+        {
+            super (_tableswitch, targets);
+            m_low = low;
+            m_high = high;
+        }
+        
+        int maxlength () { return 12 + (m_targets.length << 2); }
+        
+        void emit (final EmitCtx ctx)
+        {
+            final ByteArrayOStream out = ctx.m_out;
+            final int ip = out.size ();
+            
+            // TODO: switch to long form for any jump > 32k
+            
+            out.write (m_opcode);
+            
+            // padding bytes:
+            for (int p = 0, padCount = 3 - (ip & 3); p < padCount; ++ p) out.write (0);
+             
+            // default:
+            emitJumpOffset4 (ctx, ip, m_targets [0]);
+                        
+            // low, high:
+            final int low = m_low;
+            out.write4 (low >>> 24,  // byte1
+                        low >>> 16,  // byte2
+                        low >>> 8,   // byte3
+                        low);        // byte4
+            
+            final int high = m_high;
+            out.write4 (high >>> 24,  // byte1
+                        high >>> 16,  // byte2
+                        high >>> 8,   // byte3
+                        high);        // byte4
+                        
+            // targets:
+            for (int t = 1; t < m_targets.length; ++ t)
+            {
+                // key target:
+                emitJumpOffset4 (ctx, ip, m_targets [t]); 
+            }
+        }
+            
+        final int m_low, m_high;
+            
+    } // end of nested class
+    
+    
+    /**
+     * TODO: CodeSegment right now must be 100% position-independent code;
+     * otherwise it must follow maxlengtt() Branch pattern... 
+     */
+    static abstract class CodeSegment
+    {
+        CodeSegment (final InstrVisitor visitor)
+        {
+            m_visitor = visitor; // TODO: will this field be used?
+        }
+        
+        abstract int length ();
+        abstract int maxstack ();
+        abstract void emit (EmitCtx ctx);
+        
+        
+        final InstrVisitor m_visitor;
+        
+    } // end of nested class
+    
+    
+    static final class clinitHeader extends CodeSegment
+    {
+        clinitHeader (final InstrVisitor visitor, final int localVarIndex)
+        {
+            super (visitor);
+            final ByteArrayOStream buf = new ByteArrayOStream (CLINIT_HEADER_INIT_CAPACITY); 
+            m_buf = buf;
+            
+            final ClassDef cls = visitor.m_cls;
+            
+            final int [] blockCounts = visitor.m_classBlockCounts;
+            final int instrMethodCount = visitor.m_classInstrMethodCount; // actual number of methods to instrument may be less than the size of the block map 
+            if ($assert.ENABLED) $assert.ASSERT (blockCounts != null && blockCounts.length >= instrMethodCount,
+                "invalid block count map");
+            
+            final int coverageFieldrefIndex = visitor.m_coverageFieldrefIndex;
+            final int preclinitMethodrefIndex = visitor.m_preclinitMethodrefIndex;
+            final int classNameConstantIndex = visitor.m_classNameConstantIndex;
+            
+            if ($assert.ENABLED)
+            {
+                $assert.ASSERT (coverageFieldrefIndex > 0, "invalid coverageFieldrefIndex");
+                $assert.ASSERT (preclinitMethodrefIndex > 0, "invalid registerMethodrefIndex");
+                $assert.ASSERT (classNameConstantIndex > 0, "invalid classNameConstantIndex");
+            }
+
+            // init and load COVERAGE_FIELD:   
+            buf.write3 (_invokestatic,
+                        preclinitMethodrefIndex >>> 8,    // indexbyte1
+                        preclinitMethodrefIndex);         // indexbyte2
+
+            // [stack +1]
+
+            // TODO: disable this when there are no real blocks following?
+            // [in general, use a different template when this method contains a single block]
+
+            // TODO: if this method has been added by us, do not instrument its blocks
+            
+            // push int literal equal to 'methodID' [for the parent method]:
+            CodeGen.push_int_value (buf, cls, visitor.m_methodID);
+            
+            // [stack +2]
+            
+            // push subarray reference:
+            buf.write (_aaload);
+            
+            // [stack +1]
+            
+            // store it in alias var:
+            CodeGen.store_local_object_var (buf, localVarIndex);
+            
+            // [stack +0]            
+        }
+        
+        int length () { return m_buf.size (); }
+        int maxstack () { return 2; } // note: needs to be updated each time emitted code changes
+        
+        void emit (final EmitCtx ctx)
+        {
+            // TODO: better error handling here?
+            try
+            {
+                m_buf.writeTo (ctx.m_out);
+            }
+            catch (IOException ioe)
+            {
+                if ($assert.ENABLED) $assert.ASSERT (false, ioe.toString ());
+            }
+        }
+        
+        
+        private final ByteArrayOStream m_buf;
+        
+        private static final int CLINIT_HEADER_INIT_CAPACITY = 32; // covers about 80% of classes (no reallocation)
+        
+    } // end of nested class
+    
+    
+    static final class methodHeader extends CodeSegment
+    {
+        methodHeader (final InstrVisitor visitor, final int localVarIndex)
+        {
+            super (visitor);
+            final ByteArrayOStream buf = new ByteArrayOStream (HEADER_INIT_CAPACITY);
+            m_buf = buf;
+            
+            final ClassDef cls = visitor.m_cls;
+            final int coverageFieldrefIndex = visitor.m_coverageFieldrefIndex;
+            final int preclinitMethodrefIndex = visitor.m_preclinitMethodrefIndex;
+
+            // TODO: disable this when there are no real blocks following?
+            // [in general, use a different template when this method contains a single block]
+
+            // push ref to the static field and dup it:
+            buf.write4 (_getstatic,
+                        coverageFieldrefIndex >>> 8, // indexbyte1
+                        coverageFieldrefIndex,       // indexbyte2
+                        _dup);
+            
+            // [stack +2]
+            
+            // SF FR 971186: check if it is null and if so run the field
+            // init and class RT register code (only relevant for
+            // methods that can be executed ahead of <clinit>) [rare] 
+            
+            buf.write3 (_ifnonnull, // skip over pre-<clinit> method call
+                        0,
+                        3 + /* size of the block below */ 4);
+                        
+            // [stack +1]
+            
+            // block: call pre-<clinit> method
+            {
+                buf.write4 (_pop,
+                            _invokestatic,
+                            preclinitMethodrefIndex >>> 8,    // indexbyte1
+                            preclinitMethodrefIndex);         // indexbyte2
+                            
+                // [stack +1]
+            }
+
+            // push int literal equal to 'methodID':
+            CodeGen.push_int_value (buf, cls, visitor.m_methodID);
+            
+            // [stack +2]
+            
+            // push subarray reference:
+            buf.write (_aaload);
+            
+            // [stack +1]
+            
+            // store it in alias var:
+            CodeGen.store_local_object_var (buf, localVarIndex);
+            
+            // [stack +0]            
+        }
+        
+        int length () { return m_buf.size (); }
+        int maxstack () { return 2; } // note: needs to be updated each time emitted code changes
+        
+        void emit (final EmitCtx ctx)
+        {
+            // TODO: better error handling here?
+            try
+            {
+                m_buf.writeTo (ctx.m_out);
+            }
+            catch (IOException ioe)
+            {
+                if ($assert.ENABLED) $assert.ASSERT (false, ioe.toString ());
+            }
+        }
+        
+        
+        private final ByteArrayOStream m_buf;
+        
+        private static final int HEADER_INIT_CAPACITY = 16;
+        
+    } // end of nested class
+    
+    
+    static final class BlockSegment extends CodeSegment
+    {
+        public BlockSegment (final InstrVisitor visitor, final int localVarIndex, final int blockID)
+        {
+            super (visitor);
+            final ByteArrayOStream buf = new ByteArrayOStream (BLOCK_INIT_CAPACITY); 
+            m_buf = buf;
+                        
+            final ClassDef cls = visitor.m_cls;
+            
+            // push alias var:
+            CodeGen.load_local_object_var (buf, localVarIndex);
+            
+            // [stack +1]
+            
+            // push int value equal to 'blockID':
+            CodeGen.push_int_value (buf, cls, blockID);
+            
+            // [stack +2]
+            
+            // push boolean 'true':
+            buf.write2 (_iconst_1,
+            
+            // [stack +3]
+            
+            // store it in the array:
+                        _bastore);
+            
+            // [stack +0]
+        }
+        
+        int length () { return m_buf.size (); }
+        int maxstack () { return 3; } // note: needs to be updated each time emitted code changes
+        
+        void emit (final EmitCtx ctx)
+        {
+            // TODO: better error handling here?
+            try
+            {
+                m_buf.writeTo (ctx.m_out);
+            }
+            catch (IOException ioe)
+            {
+                if ($assert.ENABLED) $assert.ASSERT (false, ioe.toString ());
+            }
+        }
+        
+        
+        private final ByteArrayOStream m_buf;
+        
+        private static final int BLOCK_INIT_CAPACITY = 16;
+        
+    } // end of nested class
+    
+    
+    private static final class LineNumberComparator implements Comparator
+    {
+        public final int compare (final Object o1, final Object o2)
+        {
+            return ((LineNumber_info) o1).m_start_pc - ((LineNumber_info) o2).m_start_pc;
+        }
+        
+    } // end of nested class
+  
+  
+  
+    private void setClassName (final String fullName)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (fullName != null && fullName.length () > 0,
+            "null or empty input: fullName");
+        
+        final int lastSlash = fullName.lastIndexOf ('/');
+        if (lastSlash < 0)
+        {
+            m_classPackageName = "";
+            m_className = fullName;
+        }
+        else
+        {
+            if ($assert.ENABLED) $assert.ASSERT (lastSlash < fullName.length () - 1,
+                "malformed class name [" + fullName + "]");
+            
+            m_classPackageName = fullName.substring (0, lastSlash);
+            m_className = fullName.substring (lastSlash + 1);
+        }   
+    }
+    
+    private void consumeSignatureData (final int methodID, final int [] basicBlockOffsets)
+    {
+        // note: by itself, this is not a very good checksum for a class def;
+        // however, it is fast to compute and since it will be used along with
+        // a class name it should be good at detecting structural changes that
+        // matter to us (method and basic block ordering/sizes) 
+        
+        final int temp1 = basicBlockOffsets.length;
+        long temp2 = NBEAST * m_classSignature + (methodID + 1) * temp1;
+        
+        for (int i = 1; i < temp1; ++ i) // skip the initial 0 offset
+        {
+            temp2 = NBEAST * temp2 + basicBlockOffsets [i];
+        }
+        
+        m_classSignature = temp2;
+    }
+     
+    // TODO: use a compilation flag to use table assist here instead of binary search
+    // BETTER YET: use binsearch for online mode and table assist for offline [when memory is not an issue]
+  
+    /**
+     * Returns the maximum index 'i' such that (values[i] <= x). values[]
+     * contains distinct non-negative integers in increasing order. values[0] is 0,
+     * 'x' is non-negative.
+     * 
+     * Edge case:
+     *  returns values.length-1 if values [values.length - 1] < x
+     */
+    private static int lowbound (final int [] values, final int x)
+    {
+        int low = 0, high = values.length - 1;
+        
+        // assertion: lb is in [low, high]
+        
+        while (low <= high)
+        {
+            final int m = (low + high) >> 1;
+            final int v = values [m];
+            
+            if (v == x)
+                return m;
+            else if (v < x)
+                low = m + 1;
+            else // v > x
+                high = m - 1;
+        }
+        
+        return high;
+    }
+    
+    private void reset ()
+    {
+        // TODO: check that all state is reset
+        
+        m_instrument = false;
+        m_metadata = false;
+        m_ignoreAlreadyInstrumented = false;
+        
+        m_cls = null;
+        m_classPackageName = null;
+        m_className = null;
+        m_classSrcFileName = null;
+        m_classBlockMetadata = null;
+        m_classMethodDescriptors = null;
+        
+        m_syntheticStringIndex = -1;
+        m_coverageFieldrefIndex = -1;
+        m_registerMethodrefIndex = -1;
+        m_preclinitMethodrefIndex = -1;
+        m_classNameConstantIndex = -1;
+        m_clinitID = -1;
+        m_clinitStatus = 0;
+        m_classInstrMethodCount = -1;
+        m_classBlockCounts = null;
+        m_classSignature = 0;
+        
+        m_methodID = -1;
+        m_methodName = null;
+        m_methodFirstLine = 0;
+        m_methodBlockOffsets = null;
+        m_methodJumpAdjOffsets = null;
+        m_methodJumpAdjValues = null;
+    }
+    
+    
+    private final boolean m_excludeSyntheticMethods;
+    private final boolean m_excludeBridgeMethods;
+    private final boolean m_doSUIDCompensation;
+    
+    private final Logger m_log; // instr visitor logging context is latched at construction time
+    
+    // non-resettable state:
+    
+    private boolean m_warningIssued;
+    
+    
+    // resettable state:
+    
+    private boolean m_instrument;
+    private boolean m_metadata;
+    private boolean m_ignoreAlreadyInstrumented;
+    
+    /*private*/ ClassDef m_cls;
+    private String m_classPackageName; // in JVM format [com/vladium/...]; empty string for default package
+    private String m_className; // in JVM format [<init>, <clinit>, etc], relative to 'm_classPackageName'
+    private String m_classSrcFileName;
+    private int [][][] m_classBlockMetadata; // methodID->(blockID->line) map [valid only if 'm_constructMetadata' is true; null if the method has not line number table]
+    private MethodDescriptor [] m_classMethodDescriptors;
+    
+    // current class scope: 
+    private int m_syntheticStringIndex;     // index of CONSTANT_Utf8 String that reads "Synthetic"     
+    /*private*/ int m_coverageFieldrefIndex;    // index of the Fieldref for COVERAGE_FIELD
+    private int m_registerMethodrefIndex;   // index of Methodref for RT.r()
+    /*private*/ int m_preclinitMethodrefIndex;  // index of Methodref for pre-<clinit> method
+    /*private*/ int m_classNameConstantIndex;   // index of CONSTANT_String that is the class name [in JVM format]
+    private int m_stampIndex;               // index of CONSTANT_Long that is the class instr stamp
+    private int m_clinitID;                 // offset of <clinit> method [-1 if not determined yet]
+    private int m_clinitStatus;
+    /*private*/ int m_classInstrMethodCount;    // the number of slots in 'm_classBlockCounts' corresponding to methods to be instrumented for coverage
+    /*private*/ int [] m_classBlockCounts;      // basic block counts for all methods [only valid just before <clinit> is processed]
+    private long m_classSignature;
+    
+    // current method scope: 
+    /*private*/ int m_methodID;                 // offset of current method being instrumented
+    private String m_methodName;
+    private int m_methodFirstLine;
+    private int [] m_methodBlockOffsets;    // [unadjusted] basic block boundaries [length = m_classBlockCounts[m_methodID]+1; the last slot is method bytecode length]
+    private int [] m_methodBlockSizes;
+    private int [] m_methodJumpAdjOffsets;    // TODO: length ?
+    private int [] m_methodJumpAdjValues;        // TODO: length ?
+    
+    
+    private static final long NBEAST = 16661; // prime
+
+    private static final String COVERAGE_FIELD_NAME = "$VR" + "c";
+    private static final String SUID_FIELD_NAME = "serialVersionUID";
+    private static final String PRECLINIT_METHOD_NAME = "$VR" + "i";
+
+    private static final String JAVA_IO_SERIALIZABLE_NAME = "java/io/Serializable";
+    private static final String JAVA_IO_EXTERNALIZABLE_NAME = "java/io/Externalizable";
+    
+    private static final int EMIT_CTX_MIN_INIT_CAPACITY = 64; // good value determined empirically
+    private static final int PRECLINIT_INIT_CAPACITY = 128; // covers about 80% of classes (no reallocation)
+    private static final boolean MARK_ADDED_ELEMENTS_SYNTHETIC = true;
+    
+    /* It appears that nested classes and interfaces ought to be marked
+     * as Synthetic; however, neither Sun nor IBM compilers seem to do this.
+     * 
+     * (As a side note, implied no-arg constructors ought to be marked as
+     * synthetic as well, but Sun's javac is not consistent about that either)  
+     */
+    private static final boolean SKIP_SYNTHETIC_CLASSES = false;
+    
+    private static final LineNumberComparator LINE_NUMBER_COMPARATOR = new LineNumberComparator ();
+    
+    private static final byte [] EMPTY_BYTE_ARRAY = new byte [0];
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/instr/instrCommand.java b/core/java12/com/vladium/emma/instr/instrCommand.java
new file mode 100644
index 0000000..eb25312
--- /dev/null
+++ b/core/java12/com/vladium/emma/instr/instrCommand.java
@@ -0,0 +1,211 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: instrCommand.java,v 1.1.1.1.2.1 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.emma.instr;
+
+import java.io.IOException;
+
+import com.vladium.util.ClassLoaderResolver;
+import com.vladium.util.args.IOptsParser;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.Command;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class instrCommand extends Command
+{
+    // public: ................................................................
+
+    public instrCommand (final String usageToolName, final String [] args)
+    {
+        super (usageToolName, args);
+        
+        m_outMode = InstrProcessor.OutMode.OUT_MODE_COPY; // default
+    }
+
+    public synchronized void run ()
+    {
+        ClassLoader loader;
+        try
+        {
+            loader = ClassLoaderResolver.getClassLoader ();
+        }
+        catch (Throwable t)
+        {
+            loader = getClass ().getClassLoader ();
+        }
+                
+        try
+        {
+            // process 'args':
+            {
+                final IOptsParser parser = getOptParser (loader);
+                final IOptsParser.IOpts parsedopts = parser.parse (m_args);
+                
+                // check if usage is requested before checking args parse errors etc:
+                {
+                    final int usageRequestLevel = parsedopts.usageRequestLevel ();
+
+                    if (usageRequestLevel > 0)
+                    {
+                        usageexit (parser, usageRequestLevel, null);
+                        return;
+                    }
+                }
+                
+                final IOptsParser.IOpt [] opts = parsedopts.getOpts ();
+                
+                if (opts == null) // this means there were args parsing errors
+                {
+                    parsedopts.error (m_out, STDOUT_WIDTH);
+                    usageexit (parser, IOptsParser.SHORT_USAGE, null);
+                    return;
+                }
+                
+                // process parsed args:              
+                try
+                {
+                    for (int o = 0; o < opts.length; ++ o)
+                    {
+                        final IOptsParser.IOpt opt = opts [o];
+                        final String on = opt.getCanonicalName ();
+                        
+                        if (! processOpt (opt))
+                        {
+                            if ("ip".equals (on))
+                            {
+                                m_instrpath = getListOptValue (opt, PATH_DELIMITERS, true);
+                            }
+                            else if ("d".equals (on))
+                            {
+                                m_outDirName = opt.getFirstValue ();
+                            }
+                            else if ("out".equals (on))
+                            {
+                                m_outFileName = opt.getFirstValue ();
+                            }
+                            else if ("merge".equals (on))
+                            {
+                                m_outDataMerge = getOptionalBooleanOptValue (opt) ? Boolean.TRUE : Boolean.FALSE; 
+                            }
+                            else if ("ix".equals (on))
+                            {
+                                // note: this allows path delimiter in the pattern list as well
+                                m_ixpath = getListOptValue (opt, COMMA_DELIMITERS, true);
+                            }
+                            else if ("m".equals (on))
+                            {
+                                final String ov = opt.getFirstValue ();
+                                
+                                final InstrProcessor.OutMode outMode = InstrProcessor.OutMode.nameToMode (ov);
+                                if (outMode == null)
+                                {
+                                    usageexit (parser, IOptsParser.SHORT_USAGE,
+                                        "invalid '" + opts [o].getName () + "' option value: " + ov);
+                                    return;
+                                }
+                                m_outMode = outMode;
+                            }
+                        }
+                    }
+
+                    // user '-props' file property overrides:
+                    
+                    if (! processFilePropertyOverrides ()) return;
+                    
+                    // process prefixed opts:
+                    
+                    processCmdPropertyOverrides (parsedopts);
+                }
+                catch (IOException ioe)
+                {
+                    throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
+                }
+                
+                // handle cmd line-level defaults:
+                {
+                    if ($assert.ENABLED) $assert.ASSERT (m_outMode != null, "m_outMode not set");
+                    
+                    if ((m_outMode != InstrProcessor.OutMode.OUT_MODE_OVERWRITE) && (m_outDirName == null))
+                    {
+                        usageexit (parser, IOptsParser.SHORT_USAGE,
+                            "output directory must be specified for '" + m_outMode + "' output mode");
+                        return;
+                    }
+                }
+            }
+            
+            // run the instrumentor:
+            {
+                final InstrProcessor processor = InstrProcessor.create ();
+                processor.setAppName (IAppConstants.APP_NAME); // for log prefixing
+                
+                processor.setInstrPath (m_instrpath, true); // TODO: an option to set 'canonical'?
+                processor.setInclExclFilter (m_ixpath);
+                $assert.ASSERT (m_outMode != null, "m_outMode not set");
+                processor.setOutMode (m_outMode);
+                processor.setInstrOutDir (m_outDirName);
+                processor.setMetaOutFile (m_outFileName);
+                processor.setMetaOutMerge (m_outDataMerge);
+                processor.setPropertyOverrides (m_propertyOverrides);
+                
+                processor.run ();
+            }
+        }
+        catch (EMMARuntimeException yre)
+        {
+            // TODO: see below
+            
+            exit (true, yre.getMessage (), yre, RC_UNEXPECTED); // does not return
+            return;
+        }
+        catch (Throwable t)
+        {
+            // TODO: embed: OS/JVM fingerprint, build #, etc
+            // TODO: save stack trace in a file and prompt user to send it to ...
+            
+            exit (true, "unexpected failure: ", t, RC_UNEXPECTED); // does not return
+            return;
+        }
+
+        exit (false, null, null, RC_OK);
+    }    
+    
+    // protected: .............................................................
+
+
+    protected void initialize ()
+    {
+        super.initialize ();
+    }
+    
+    protected String usageArgsMsg ()
+    {
+        return "[options]";
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+        
+    private String [] m_instrpath;
+    private String [] m_ixpath;
+    private String m_outDirName;
+    private String m_outFileName;
+    private Boolean m_outDataMerge;
+    private InstrProcessor.OutMode m_outMode;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/AbstractItemVisitor.java b/core/java12/com/vladium/emma/report/AbstractItemVisitor.java
new file mode 100644
index 0000000..a63176a
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/AbstractItemVisitor.java
@@ -0,0 +1,52 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AbstractItemVisitor.java,v 1.1.1.1 2004/05/09 16:57:36 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class AbstractItemVisitor implements IItemVisitor
+{
+    // public: ................................................................
+
+    public Object visit (final AllItem item, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final PackageItem item, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final SrcFileItem item, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final ClassItem item, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final MethodItem item, final Object ctx)
+    {
+        return ctx;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/AbstractReportGenerator.java b/core/java12/com/vladium/emma/report/AbstractReportGenerator.java
new file mode 100644
index 0000000..6e37179
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/AbstractReportGenerator.java
@@ -0,0 +1,258 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AbstractReportGenerator.java,v 1.1.1.1.2.4 2005/04/24 23:51:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.Descriptors;
+import com.vladium.util.IProperties;
+import com.vladium.util.IntIntMap;
+import com.vladium.util.IntVector;
+import com.vladium.util.ObjectIntMap;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.ClassDescriptor;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.IMetaData;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class AbstractReportGenerator extends AbstractItemVisitor
+                                       implements IReportGenerator
+{
+    // public: ................................................................
+    
+    
+    public static IReportGenerator create (final String type)
+    {
+        if ((type == null) || (type.length () == 0))
+            throw new IllegalArgumentException ("null/empty input: type");
+        
+        // TODO: proper pluggability pattern here
+        
+        if ("html".equals (type))
+            return new com.vladium.emma.report.html.ReportGenerator ();
+        else if ("txt".equals (type))
+            return new com.vladium.emma.report.txt.ReportGenerator ();
+        else if ("xml".equals (type))
+            return new com.vladium.emma.report.xml.ReportGenerator ();
+        else // TODO: error code
+            throw new EMMARuntimeException ("no report generator class found for type [" + type + "]");
+    }
+    
+    
+    public void initialize (final IMetaData mdata, final ICoverageData cdata,
+                            final SourcePathCache cache, final IProperties properties)
+        throws EMMARuntimeException
+    {
+        m_log = Logger.getLogger ();
+        m_verbose = m_log.atVERBOSE ();
+        
+        m_settings = ReportProperties.parseProperties (properties, getType ());
+        
+        m_cache = cache;
+                
+        m_hasSrcFileInfo = mdata.hasSrcFileData ();
+        m_hasLineNumberInfo = mdata.hasLineNumberData ();
+        
+        boolean debugInfoWarning = false;
+        boolean bailOut = false;
+        
+        // src view is not possible if 'm_hasSrcFileInfo' is false:        
+        if (! mdata.hasSrcFileData () && (m_settings.getViewType () == IReportDataView.HIER_SRC_VIEW))
+        {
+            debugInfoWarning = true;
+            
+            m_log.warning ("not all instrumented classes were compiled with source file");
+            m_log.warning ("debug data: no sources will be embedded in the report.");
+            
+            m_settings.setViewType (IReportDataView.HIER_CLS_VIEW);
+        }
+        
+        // line coverage column must be removed if 'm_hasLineNumberInfo' is false:
+        if (! m_hasLineNumberInfo)
+        {
+            final int [] userColumnIDs = m_settings.getColumnOrder ();
+            final IntVector columnIDs = new IntVector ();
+            
+            boolean removed = false;
+            for (int c = 0; c < userColumnIDs.length; ++ c)
+            {
+                if (userColumnIDs [c] == IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID)
+                    removed = true;
+                else
+                    columnIDs.add (userColumnIDs [c]);
+            }
+            
+            // at this point it is possible that there are no columns left: bail out
+            if (removed)
+            {
+                debugInfoWarning = true;
+                
+                if (columnIDs.size () == 0)
+                {
+                    m_log.warning ("line coverage requested in a report of type [" + getType () + "] but");
+                    m_log.warning ("not all instrumented classes were compiled with line number");
+                    m_log.warning ("debug data: since this was the only requested column, no report will be generated.");
+
+                    bailOut = true;
+                }
+                else
+                {
+                    m_log.warning ("line coverage requested in a report of type [" + getType () + "] but");
+                    m_log.warning ("not all instrumented classes were compiled with line number");
+                    m_log.warning ("debug data: this column will be removed from the report.");
+                    
+                    m_settings.setColumnOrder (columnIDs.values ());
+                    
+                    final int [] userSort = m_settings.getSortOrder ();
+                    final IntVector sort = new IntVector ();
+                    
+                    for (int c = 0; c < userSort.length; c += 2)
+                    {
+                        if (Math.abs (userSort [c]) != IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID)
+                        {
+                            sort.add (userSort [c]);
+                            sort.add (userSort [c + 1]);
+                        }
+                    }
+                    
+                    m_settings.setSortOrder (sort.values ());
+                }
+            }
+        }
+        // note: no need to adjust m_metrics due to possible column removal above
+        
+        // SF FR 971176: provide user with sample classes that caused the above warnings
+        if (debugInfoWarning && m_log.atINFO ())
+        {
+            final Set /* String */ sampleClassNames = new TreeSet ();
+            final ObjectIntMap /* packageVMName:String -> count:int */ countMap = new ObjectIntMap ();
+            final int [] _count = new int [1];
+            
+            for (Iterator /* ClassDescriptor */ descriptors = mdata.iterator (); descriptors.hasNext (); )
+            {    
+                final ClassDescriptor cls = (ClassDescriptor) descriptors.next ();
+                
+                // SF BUG 979717: this check was incorrectly absent in the initial FR impl:
+                if (! cls.hasCompleteLineNumberInfo () || ! cls.hasSrcFileInfo ())
+                {
+                    final String packageVMName = cls.getPackageVMName ();
+                    final int count = countMap.get (packageVMName, _count)
+                        ? _count [0]
+                        : 0;
+                    
+                    if (count < MAX_DEBUG_INFO_WARNING_COUNT)
+                    {
+                        sampleClassNames.add (Descriptors.vmNameToJavaName (cls.getClassVMName ()));
+                        countMap.put (packageVMName, count + 1);
+                    }
+                }
+            }
+            
+            m_log.info ("showing up to " + MAX_DEBUG_INFO_WARNING_COUNT + " classes without full debug info per package:");
+            for (Iterator /* String */ names = sampleClassNames.iterator (); names.hasNext (); )
+            {
+                m_log.info ("  " + names.next ());
+            }
+        }
+        
+        if (bailOut)
+        {
+            // TODO: error code
+            throw new EMMARuntimeException ("BAILED OUT");
+        }
+        
+        final IItemMetadata [] allTypes = IItemMetadata.Factory.getAllTypes ();
+        m_typeSortComparators = new ItemComparator [allTypes.length];
+        
+        for (int t = 0; t < allTypes.length; ++ t)
+        {
+            final IntVector orderedAttrIDsWithDir = new IntVector ();
+            final long typeAttrIDSet = allTypes [t].getAttributeIDs ();
+            
+            for (int s = 0; s < m_settings.getSortOrder ().length; s += 2)
+            {
+                final int attrID = m_settings.getSortOrder () [s];
+                
+                if ((typeAttrIDSet & (1 << attrID)) != 0)
+                {
+                    orderedAttrIDsWithDir.add (attrID);
+                    
+                    final int dir = m_settings.getSortOrder () [s + 1];
+                    orderedAttrIDsWithDir.add (dir);
+                }
+            }
+            
+            m_typeSortComparators [t] = ItemComparator.Factory.create (orderedAttrIDsWithDir.values (), m_settings.getUnitsType ());
+        }
+        
+        m_metrics = new int [allTypes.length];
+        final IntIntMap metrics = m_settings.getMetrics ();
+        for (int t = 0; t < m_metrics.length; ++ t)
+        {
+            m_metrics [t] = -1;
+            metrics.get (t, m_metrics, t);
+        }
+        
+        final IReportDataModel model = IReportDataModel.Factory.create (mdata, cdata);
+        m_view = model.getView (m_settings.getViewType ());
+        
+        m_srcView = (m_settings.getViewType () == IReportDataView.HIER_SRC_VIEW);
+    }
+    
+    public void cleanup ()
+    {
+        reset ();
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected void reset ()
+    {
+        m_settings = null;
+        m_cache = null;
+        m_view = null;
+        m_srcView = false;
+        
+        //m_typeSortIDs = null;
+        m_typeSortComparators = null;
+        m_metrics = null;
+        
+        m_log = null;
+    }
+    
+
+    protected ReportProperties.ParsedProperties m_settings;
+    protected SourcePathCache m_cache;
+    protected IReportDataView m_view;
+    protected boolean m_srcView;
+    
+    protected boolean m_hasSrcFileInfo, m_hasLineNumberInfo;
+    protected ItemComparator [] m_typeSortComparators; // m_typeSortComparators [t] is a comparator representing the sort order for item type t 
+    protected int [] m_metrics; // -1 means no pass/fail check for this attribute
+    
+    protected Logger m_log; // every report generator is used on a single thread but the logger needs to be run()-scoped
+    protected boolean m_verbose;
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final int MAX_DEBUG_INFO_WARNING_COUNT = 3; // per package
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/AllItem.java b/core/java12/com/vladium/emma/report/AllItem.java
new file mode 100644
index 0000000..46ce575
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/AllItem.java
@@ -0,0 +1,66 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AllItem.java,v 1.1.1.1 2004/05/09 16:57:36 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class AllItem extends Item
+{
+    // public: ................................................................
+    
+    public AllItem ()
+    {
+        super (null);
+    }
+    
+    public String getName ()
+    {
+        return "all classes";
+    }
+        
+    public void accept (final IItemVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    // TODO: remove these instance methods returning static data
+    public final IItemMetadata getMetadata ()
+    {
+        return METADATA;
+    }
+
+    public static IItemMetadata getTypeMetadata ()
+    {
+        return METADATA;
+    }
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final Item.ItemMetadata METADATA; // set in <clinit>
+        
+    static
+    {        
+        METADATA = new Item.ItemMetadata (IItemMetadata.TYPE_ID_ALL, "all",
+            1 << IItemAttribute.ATTRIBUTE_NAME_ID |
+            1 << IItemAttribute.ATTRIBUTE_CLASS_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID);
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/ClassItem.java b/core/java12/com/vladium/emma/report/ClassItem.java
new file mode 100644
index 0000000..f3423cf
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/ClassItem.java
@@ -0,0 +1,246 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassItem.java,v 1.1.1.1.2.1 2004/06/20 20:07:22 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.Iterator;
+
+import com.vladium.util.IntObjectMap;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.data.ClassDescriptor;
+import com.vladium.emma.data.MethodDescriptor;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ClassItem extends Item
+{
+    // public: ................................................................
+    
+    public ClassItem (final IItem parent, final ClassDescriptor cls, final boolean [][] coverage)
+    {
+        super (parent);
+        
+        m_cls = cls;
+        m_coverage = coverage;
+    }
+    
+    public String getName ()
+    {
+        return m_cls.getName ();
+    }
+    
+    public String getSrcFileName ()
+    {
+        return m_cls.getSrcFileName ();
+    }
+    
+    // TODO: SrcFileItem could benefit from this method for its own getFirstLine()
+    public int getFirstLine ()
+    {
+        // TODO: state validation
+        
+        if (m_firstLine == 0)
+        {
+            final MethodDescriptor [] methods = m_cls.getMethods ();
+            
+            int firstLine = Integer.MAX_VALUE;
+            for (int m = 0, mLimit = methods.length; m < mLimit; ++ m)
+            {
+                final int mFirstLine = methods [m].getFirstLine ();
+                if ((mFirstLine > 0) && (mFirstLine < firstLine))
+                    firstLine = mFirstLine;
+            }
+            
+            m_firstLine = firstLine;
+            return firstLine;
+        }
+        
+        return m_firstLine;
+    }
+        
+    public ClassDescriptor getClassDescriptor ()
+    {
+        return m_cls;
+    }
+    
+    public boolean [][] getCoverage ()
+    {
+        return m_coverage;
+    }
+    
+    public boolean loaded ()
+    {
+        return m_coverage != null;
+    }
+    
+    public int getAggregate (final int type)
+    {
+        final int [] aggregates = m_aggregates;
+
+        int value = aggregates [type];
+        
+        if (value < 0)
+        {
+            switch (type)
+            {
+                case COVERAGE_CLASS_COUNT:
+                case    TOTAL_CLASS_COUNT:
+                {
+                    aggregates [TOTAL_CLASS_COUNT] = 1;
+                    aggregates [COVERAGE_CLASS_COUNT] = m_coverage != null ? 1 : 0;
+                    
+                    return aggregates [type];
+                }
+                //break;
+           
+           
+                case COVERAGE_LINE_COUNT:
+                case    TOTAL_LINE_COUNT:
+                
+                case COVERAGE_LINE_INSTR:
+                {
+                    // line aggregate types are special when used on clsfile items:
+                    // unlike all others, they do not simply add up when the line
+                    // info is available; instead, lines from all methods belonging
+                    // to the same clsfile parent are set-merged 
+                    
+                    final boolean [][] ccoverage = m_coverage; // this can be null
+                    
+                    final IntObjectMap /* line -> int[2] */ cldata = new IntObjectMap ();
+                    final MethodDescriptor [] methoddescs = m_cls.getMethods ();
+                        
+                    for (Iterator methods = getChildren (); methods.hasNext (); )
+                    {
+                        final MethodItem method = (MethodItem) methods.next ();
+                        final int methodID = method.getID ();
+                        
+                        final boolean [] mcoverage = ccoverage == null ? null : ccoverage [methodID];
+                        
+                        final MethodDescriptor methoddesc = methoddescs [methodID];                        
+                        final int [] mbsizes = methoddesc.getBlockSizes ();
+                        final IntObjectMap mlineMap = methoddesc.getLineMap ();
+                        if ($assert.ENABLED) $assert.ASSERT (mlineMap != null);
+                        
+
+                        final int [] mlines = mlineMap.keys ();
+                        for (int ml = 0, mlLimit = mlines.length; ml < mlLimit; ++ ml)
+                        {
+                            final int mline = mlines [ml];
+                            
+                            int [] data = (int []) cldata.get (mline);
+                            if (data == null)
+                            {
+                                data = new int [4]; // { totalcount, totalinstr, coveragecount, coverageinstr }
+                                cldata.put (mline, data);
+                            }
+                            
+                            final int [] lblocks = (int []) mlineMap.get (mline);
+                            
+                            final int bCount = lblocks.length; 
+                            data [0] += bCount;
+                            
+                            for (int bID = 0; bID < bCount; ++ bID)
+                            {
+                                final int block = lblocks [bID];
+                                
+                                final boolean bcovered = mcoverage != null && mcoverage [block];
+                                final int instr = mbsizes [block];
+                                
+                                data [1] += instr;
+                                if (bcovered)
+                                {
+                                    ++ data [2];
+                                    data [3] += instr;
+                                }
+                            }
+                        }
+                    }
+                    
+                    aggregates [TOTAL_LINE_COUNT] = cldata.size ();
+                    
+                    int coverageLineCount = 0;
+                    int coverageLineInstr = 0;
+                    
+                    final int [] clines = cldata.keys ();
+                    for (int cl = 0, clLimit = clines.length; cl < clLimit; ++ cl)
+                    {
+                        final int cline = clines [cl];
+                        final int [] data = (int []) cldata.get (cline);
+                        
+                        final int ltotalCount = data [0];
+                        final int ltotalInstr = data [1];
+                        final int lcoverageCount = data [2];
+                        final int lcoverageInstr = data [3];
+                        
+                        if (lcoverageInstr > 0)
+                        {
+                            coverageLineCount += (PRECISION * lcoverageCount) / ltotalCount;
+                            coverageLineInstr += (PRECISION * lcoverageInstr) / ltotalInstr;
+                        }
+                    }
+                    
+                    aggregates [COVERAGE_LINE_COUNT] = coverageLineCount;
+                    aggregates [COVERAGE_LINE_INSTR] = coverageLineInstr;
+                    
+                    return aggregates [type];
+                }
+                //break;
+                
+                               
+                default: return super.getAggregate (type);
+            }
+        }
+        
+        return value;
+    }
+    
+    public void accept (final IItemVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public final IItemMetadata getMetadata ()
+    {
+        return METADATA;
+    }
+    
+    public static IItemMetadata getTypeMetadata ()
+    {
+        return METADATA;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    final ClassDescriptor m_cls;
+    final boolean [][] m_coverage;
+    
+    // private: ...............................................................
+    
+    
+    private int m_firstLine;
+    
+    private static final Item.ItemMetadata METADATA; // set in <clinit>
+        
+    static
+    {
+        METADATA = new Item.ItemMetadata (IItemMetadata.TYPE_ID_CLASS, "class",
+            1 << IItemAttribute.ATTRIBUTE_NAME_ID |
+            1 << IItemAttribute.ATTRIBUTE_CLASS_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID);
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IItem.java b/core/java12/com/vladium/emma/report/IItem.java
new file mode 100644
index 0000000..4d5bed3
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IItem.java
@@ -0,0 +1,75 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IItem.java,v 1.1.1.1.2.1 2005/06/12 22:43:11 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.Iterator;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IItem
+{
+    // public: ................................................................
+    
+    // TODO: consider making this an abstact class [merge into Item]
+    
+    // note: this design does not enforce all items at the same level being of the same 'type' (class, pkg, method, etc)    
+
+    IItem getParent ();
+    int getChildCount ();
+    Iterator /* IItem */ getChildren ();
+    /**
+     * 
+     * @param order [null is equivalent to no sort]
+     * @return
+     */
+    Iterator /* IItem */ getChildren (ItemComparator /* IItem */ order);
+    
+    String getName ();   
+    IItemMetadata getMetadata ();
+    IItemAttribute getAttribute (int attributeID, int unitsID);
+    int getAggregate (int type);
+    
+    void accept (IItemVisitor visitor, Object ctx);
+
+
+    // TODO: move these elsewhere and fix gaps
+        // WARNING: careful about reordering!
+    
+        // (coverage data) measured in counts:
+        int COVERAGE_CLASS_COUNT    = 5; // count of class loads
+        int COVERAGE_METHOD_COUNT   = 4; // count of method entries
+        
+        // (coverage data) measured in counts or instrs:
+        int COVERAGE_BLOCK_COUNT    = 0; // in count units
+        int COVERAGE_LINE_COUNT     = 1; // in count units
+        int COVERAGE_BLOCK_INSTR    = 2; // in instr units
+        int COVERAGE_LINE_INSTR     = 3; // total line instr coverage, scaled up by PRECISION
+        
+    
+        // (metadata) measured in counts:
+        int TOTAL_CLASS_COUNT       = 11;
+        int TOTAL_METHOD_COUNT      = 10;
+    
+        // (metadata) measured in counts or instrs:
+        int TOTAL_BLOCK_COUNT       = 6; // in count units
+        int TOTAL_LINE_COUNT        = 7; // in count units
+        int TOTAL_BLOCK_INSTR       = 8; // in instr units
+        //int TOTAL_LINE_INSTR        = 9; // in instr units
+       
+        int TOTAL_SRCFILE_COUNT     = 12;
+        //int TOTAL_SRCLINE_COUNT     = 13;
+        
+        int NUM_OF_AGGREGATES = TOTAL_SRCFILE_COUNT + 1;
+        int PRECISION = 100; // BUG_SF988160: increase overflow safety margin for very large projects  
+   
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IItemAttribute.java b/core/java12/com/vladium/emma/report/IItemAttribute.java
new file mode 100644
index 0000000..1d8777d
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IItemAttribute.java
@@ -0,0 +1,266 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IItemAttribute.java,v 1.1.1.1.2.1 2004/07/10 19:08:50 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.util.Comparator;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IItemAttribute
+{
+    // public: ................................................................
+    
+    // TODO: this modeling (units is an independent axis) is not ideal, because
+    // not all of the attributes have meaningful separation by unit type
+    
+    // note: the ordering is consistent with code in Factory:
+    
+    int ATTRIBUTE_NAME_ID               = 0;
+    int ATTRIBUTE_CLASS_COVERAGE_ID     = 1;
+    int ATTRIBUTE_METHOD_COVERAGE_ID    = 2;
+    int ATTRIBUTE_BLOCK_COVERAGE_ID     = 3;
+    int ATTRIBUTE_LINE_COVERAGE_ID      = 4;
+    
+    // note: the ordering is consistent with code in Factory and SrcFileItem:
+    
+    int UNITS_COUNT = 0;
+    int UNITS_INSTR = 1;    
+    
+    Comparator /* IItem */ comparator ();
+    String getName ();
+    void format (IItem item, StringBuffer appendTo);
+    boolean passes (IItem item, int criterion); // ideally, criteria should come from a double-dispatched API
+    
+    
+    abstract class Factory
+    {
+        public static IItemAttribute getAttribute (final int attributeID, final int unitsID)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (attributeID >= ATTRIBUTE_NAME_ID && attributeID <= ATTRIBUTE_LINE_COVERAGE_ID, "invalid attribute ID: " + attributeID);
+            if ($assert.ENABLED) $assert.ASSERT (unitsID >= UNITS_COUNT && unitsID <= UNITS_INSTR, "invalid units ID: " + unitsID);
+            
+            return ATTRIBUTES [unitsID][attributeID];
+        }
+        
+        public static IItemAttribute [] getAttributes (final int unitsID)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (unitsID >= UNITS_COUNT && unitsID <= UNITS_INSTR, "invalid units ID: " + unitsID);
+            
+            return (IItemAttribute []) ATTRIBUTES [unitsID].clone ();
+        }
+        
+        private static abstract class Attribute implements IItemAttribute
+        {
+            public String getName ()
+            {
+                return m_name;
+            }
+            
+             protected Attribute (final String name)
+            {
+                if (name == null) throw new IllegalArgumentException ("null input: name");
+                
+                m_name = name;
+            }
+
+            
+            private final String m_name;
+            
+        } // end of nested class
+        
+        
+        private static final class NameAttribute extends Attribute
+                                                 implements IItemAttribute
+        {
+            public Comparator comparator ()
+            {
+                return m_comparator;
+            }  
+    
+            public void format (final IItem item, final StringBuffer appendTo)
+            {
+                appendTo.append (item.getName ());
+            }
+            
+            public boolean passes (final IItem item, final int criterion)
+            {
+                return true; // names always pass [for now]
+            }
+
+            
+            private static final class NameComparator implements Comparator
+            {
+                public int compare (final Object l, final Object g)
+                {
+                    final IItem il = (IItem) l;
+                    final IItem ig = (IItem) g;
+                                        
+                    return il.getName ().compareTo (ig.getName ());
+                }
+
+            } // end of nested class
+            
+            NameAttribute (final String name)
+            {
+                super (name);
+                
+                m_comparator = new NameComparator ();
+            }
+            
+            
+            private final Comparator m_comparator;
+            
+        } // end of nested class
+        
+        
+        private static final class FractionAttribute extends Attribute
+                                                     implements IItemAttribute
+        {
+            public Comparator comparator ()
+            {
+                return m_comparator;
+            }  
+    
+            public void format (final IItem item, final StringBuffer appendTo)
+            {
+                final int n = item.getAggregate (m_numeratorAggregateID);
+                final double n_scaled = (double) n / m_scale;
+                final int d = item.getAggregate (m_denominatorAggregateID);
+                
+                // d can be 0 legally: the compiler can generate classes with no methods in them [happens with synthetic classes, for example]
+                //if ($assert.ENABLED) $assert.ASSERT (d > 0, "[attr ID = " + m_denominatorAggregateID + "] invalid denominator: " + d);
+
+                final int appendToStart = appendTo.length ();
+
+                if (d == 0)
+                    m_format.format (1.0F, appendTo, m_fieldPosition);
+                else
+                    m_format.format (n_scaled / d, appendTo, m_fieldPosition);
+                
+                final int iLimit = Math.max (1, 5 - appendTo.length () + appendToStart);
+                for (int i = 0; i < iLimit; ++ i) appendTo.append (' ');
+                
+                appendTo.append ('(');
+                m_nFormat.format (n_scaled, appendTo, m_fieldPosition);
+                appendTo.append ('/');
+                appendTo.append (d);
+                appendTo.append (')');
+            }
+            
+            public boolean passes (final IItem item, final int criterion)
+            {
+                final int n = item.getAggregate (m_numeratorAggregateID);
+                final int d = item.getAggregate (m_denominatorAggregateID);
+                
+                return ((double) n) * IItem.PRECISION >=  ((double) d) * m_scale * criterion; 
+            }
+            
+            
+            private final class FractionComparator implements Comparator
+            {
+                public int compare (final Object l, final Object g)
+                {
+                    final IItem il = (IItem) l;
+                    final IItem ig = (IItem) g;
+                    
+                    final double nil = il.getAggregate (m_numeratorAggregateID);
+                    final double dil = il.getAggregate (m_denominatorAggregateID);
+                    
+                    final double nig = ig.getAggregate (m_numeratorAggregateID);
+                    final double dig = ig.getAggregate (m_denominatorAggregateID);
+                    
+                    final double diff = nil * dig - nig * dil; 
+
+                    return diff > 0.0 ? +1 : (diff < 0.0 ? -1 : 0);
+                }
+                
+            } // end of inner class
+            
+            
+            FractionAttribute (final String name, final int numeratorAggregateID, final int denominatorAggregateID, final int scale, final int nFractionDigits)
+            {
+                super (name);
+                
+                if ($assert.ENABLED) $assert.ASSERT (scale != 0, "scale: " + scale);
+
+                m_numeratorAggregateID = numeratorAggregateID;
+                m_denominatorAggregateID = denominatorAggregateID; // ok to be zero
+                m_scale = scale;
+                
+                m_format = (DecimalFormat) NumberFormat.getPercentInstance (); // TODO: locale
+                m_fieldPosition = new FieldPosition (DecimalFormat.INTEGER_FIELD);
+                
+                // TODO: set this from a pattern property
+                //m_format.setMinimumFractionDigits (1);
+                m_format.setMaximumFractionDigits (0);
+                //m_format.setDecimalSeparatorAlwaysShown (false);
+                
+                m_nFormat = (DecimalFormat) NumberFormat.getInstance (); // TODO: locale
+                m_nFormat.setGroupingUsed (false);
+                m_nFormat.setMaximumFractionDigits (nFractionDigits);
+                
+                m_comparator = new FractionComparator ();
+            }
+            
+            
+            final int m_numeratorAggregateID, m_denominatorAggregateID;
+            
+            private final int m_scale;
+            private final DecimalFormat m_format, m_nFormat;
+            private final FieldPosition m_fieldPosition;
+            private final Comparator m_comparator; 
+            
+        } // end of nested class
+
+
+
+        private Factory () {}
+        
+        private static final IItemAttribute [/* unit */][/* attributes */] ATTRIBUTES; // set in <clinit> 
+        
+        static
+        {
+            final IItemAttribute nameAttribute = new NameAttribute ("name");
+            
+            final IItemAttribute classCoverageAttribute = new FractionAttribute ("class, %", IItem.COVERAGE_CLASS_COUNT, IItem.TOTAL_CLASS_COUNT, 1, 0);
+            final IItemAttribute methodCoverageAttribute = new FractionAttribute ("method, %", IItem.COVERAGE_METHOD_COUNT, IItem.TOTAL_METHOD_COUNT, 1, 0);
+            
+            ATTRIBUTES = new IItemAttribute [][]
+            {
+                /* count: */
+                {
+                    nameAttribute,
+                    classCoverageAttribute,
+                    methodCoverageAttribute,
+                    new FractionAttribute ("block, %", IItem.COVERAGE_BLOCK_COUNT, IItem.TOTAL_BLOCK_COUNT, 1, 0),
+                    new FractionAttribute ("line, %", IItem.COVERAGE_LINE_COUNT, IItem.TOTAL_LINE_COUNT, IItem.PRECISION, 1),
+                },
+                /* instr: */
+                {
+                    nameAttribute,
+                    classCoverageAttribute,
+                    methodCoverageAttribute,
+                    new FractionAttribute ("block, %", IItem.COVERAGE_BLOCK_INSTR, IItem.TOTAL_BLOCK_INSTR, 1, 0),
+                    new FractionAttribute ("line, %", IItem.COVERAGE_LINE_INSTR, IItem.TOTAL_LINE_COUNT, IItem.PRECISION, 1),
+                },
+            };
+        }
+        
+    } // end of nested class
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IItemMetadata.java b/core/java12/com/vladium/emma/report/IItemMetadata.java
new file mode 100644
index 0000000..e0f3e50
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IItemMetadata.java
@@ -0,0 +1,76 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IItemMetadata.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IItemMetadata
+{
+    // public: ................................................................
+
+    // note: order is in sync with Factory init code    
+    int TYPE_ID_ALL         = 0;
+    int TYPE_ID_PACKAGE     = 1;
+    int TYPE_ID_SRCFILE     = 2;
+    int TYPE_ID_CLASS       = 3;
+    int TYPE_ID_METHOD      = 4;
+    
+    int getTypeID ();
+    String getTypeName ();
+    
+    /**
+     * Using a long is only ok for less than 64 global attributes, but this limit
+     * seems ok for a long time to come. 
+     * 
+     * @return bitmask for valid attributes
+     */
+    long getAttributeIDs ();
+    
+    abstract class Factory
+    {
+        public static IItemMetadata getTypeMetadata (final int typeID)
+        {
+            if ((typeID < TYPE_ID_ALL) || (typeID > TYPE_ID_METHOD))
+                throw new IllegalArgumentException ("invalid type ID: " + typeID);
+                
+            return METADATA [typeID];
+        }
+        
+        public static IItemMetadata [] getAllTypes ()
+        {
+            return METADATA;
+        }
+        
+        private Factory () {}
+        
+        
+        private static final IItemMetadata [] METADATA; // set in <clinit>
+        
+        static
+        {
+            // this establishes the mapping TYPE_ID_xxx->metadata for type xxx:
+            
+            METADATA = new IItemMetadata []
+            {
+                AllItem.getTypeMetadata (),
+                PackageItem.getTypeMetadata (),
+                SrcFileItem.getTypeMetadata (),
+                ClassItem.getTypeMetadata (),
+                MethodItem.getTypeMetadata (),
+            };
+        }
+        
+    } // end of nested class 
+    
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IItemVisitor.java b/core/java12/com/vladium/emma/report/IItemVisitor.java
new file mode 100644
index 0000000..eec3333
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IItemVisitor.java
@@ -0,0 +1,26 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IItemVisitor.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public interface IItemVisitor
+{
+    // public: ................................................................
+    
+    Object visit (AllItem item, Object ctx);
+    Object visit (PackageItem item, Object ctx);
+    Object visit (SrcFileItem item, Object ctx);
+    Object visit (ClassItem item, Object ctx);
+    Object visit (MethodItem item, Object ctx);
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IReportDataModel.java b/core/java12/com/vladium/emma/report/IReportDataModel.java
new file mode 100644
index 0000000..9b0b8b3
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IReportDataModel.java
@@ -0,0 +1,39 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IReportDataModel.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.IMetaData;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IReportDataModel
+{
+    // public: ................................................................
+    
+    IReportDataView getView (int viewType);
+    
+    abstract class Factory
+    {
+        /**
+         * This operation merely stores mdata and cdata references, it does not
+         * perform any data processing until getView() is actually called.
+         */
+        public static IReportDataModel create (final IMetaData mdata, final ICoverageData cdata)
+        {
+            return new ReportDataModel (mdata, cdata);
+        }
+        
+    } // end of nested class
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IReportDataView.java b/core/java12/com/vladium/emma/report/IReportDataView.java
new file mode 100644
index 0000000..4b32bfe
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IReportDataView.java
@@ -0,0 +1,26 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IReportDataView.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public interface IReportDataView
+{
+    // public: ................................................................
+    
+
+    int HIER_CLS_VIEW   = 0;
+    int HIER_SRC_VIEW   = 1;   
+    
+    IItem getRoot ();
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IReportGenerator.java b/core/java12/com/vladium/emma/report/IReportGenerator.java
new file mode 100644
index 0000000..4dc2a3c
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IReportGenerator.java
@@ -0,0 +1,35 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IReportGenerator.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import com.vladium.util.IProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.data.ICoverageData;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IReportGenerator
+{
+    // public: ................................................................
+    
+    String getType ();
+    
+    // TODO: make sure reporters are reusable
+    
+    void process (IMetaData mdata, ICoverageData cdata, SourcePathCache cache, IProperties parameters)
+        throws EMMARuntimeException;
+    
+    void cleanup ();
+        
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/IReportProperties.java b/core/java12/com/vladium/emma/report/IReportProperties.java
new file mode 100644
index 0000000..6d5f963
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/IReportProperties.java
@@ -0,0 +1,82 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IReportProperties.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IReportProperties
+{
+    // public: ................................................................
+   
+    // TODO: separate props for diff kinds of files (m, c, reports) ?
+    
+    String PREFIX = "report.";
+    
+    // parameter:
+    String OUT_ENCODING     = "out.encoding";
+    String OUT_DIR          = "out.dir";
+    String OUT_FILE         = "out.file";
+
+    // parameter:
+    String UNITS_TYPE       = "units";
+    // values:
+    String COUNT_UNITS      = "count";
+    String INSTR_UNITS      = "instr";
+        
+    // parameter:
+    String VIEW_TYPE        = "view";
+    // values:
+    String CLS_VIEW         = "class";
+    String SRC_VIEW         = "source";
+
+    // parameter:
+    String HIDE_CLASSES     = "hideclasses"; // boolean
+    
+    // parameter:
+    String DEPTH            = "depth";
+    // values:
+    String DEPTH_ALL        = "all";
+    String DEPTH_PACKAGE    = "package";
+    String DEPTH_SRCFILE    = "source";
+    String DEPTH_CLASS      = "class";
+    String DEPTH_METHOD     = "method";
+    
+    // parameter:
+    String COLUMNS          = "columns"; // comma-separated list
+    // values:
+    String ITEM_NAME_COLUMN         = "name";
+    String CLASS_COVERAGE_COLUMN    = "class";
+    String METHOD_COVERAGE_COLUMN   = "method";
+    String BLOCK_COVERAGE_COLUMN    = "block";
+    String LINE_COVERAGE_COLUMN     = "line";
+    
+    // parameter:
+    String SORT             = "sort"; // comma-separated list of ('+'/'-'-prefixed column names)
+    char ASC                = '+'; // default
+    char DESC               = '-';
+    
+    // parameter:
+    String METRICS          = "metrics"; // comma-separated list of (column name:metric) pairs
+    char MSEPARATOR         = ':';
+
+    // defaults:
+    
+    String DEFAULT_UNITS_TYPE = INSTR_UNITS;
+    String DEFAULT_VIEW_TYPE = SRC_VIEW;
+    String DEFAULT_HIDE_CLASSES = "true";
+    String DEFAULT_DEPTH = DEPTH_PACKAGE;
+    String DEFAULT_COLUMNS = CLASS_COVERAGE_COLUMN + "," + METHOD_COVERAGE_COLUMN + "," + BLOCK_COVERAGE_COLUMN + "," + LINE_COVERAGE_COLUMN + "," + ITEM_NAME_COLUMN;
+    String DEFAULT_SORT = ASC + BLOCK_COVERAGE_COLUMN + "," + ASC + ITEM_NAME_COLUMN;
+    String DEFAULT_METRICS = METHOD_COVERAGE_COLUMN + MSEPARATOR + "70," + BLOCK_COVERAGE_COLUMN + MSEPARATOR + "80," + LINE_COVERAGE_COLUMN + MSEPARATOR + "80," + CLASS_COVERAGE_COLUMN + MSEPARATOR + "100";
+
+} // end of inteface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/Item.java b/core/java12/com/vladium/emma/report/Item.java
new file mode 100644
index 0000000..182a24a
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/Item.java
@@ -0,0 +1,162 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Item.java,v 1.1.1.1.2.1 2004/06/20 20:14:39 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+abstract class Item implements IItem
+{
+    // public: ................................................................
+
+    
+    // IItem:
+    
+    public final int getChildCount ()
+    {
+        return m_children.size ();
+    }
+
+    public final IItem getParent ()
+    {
+        return m_parent;
+    }
+
+    public final Iterator getChildren ()
+    {
+        return m_children.iterator ();
+    }
+    
+    public final Iterator getChildren (final ItemComparator /* IItem */ order)
+    {
+        // TODO: soft caching keyed off 'order'
+        
+        if (order == null)
+            return getChildren ();
+        else
+        {        
+            final IItem [] items = new IItem [m_children.size ()];
+            m_children.toArray (items);
+            
+            Arrays.sort (items, order);
+            
+            return Arrays.asList (items).iterator ();
+        }
+    }
+    
+    public final IItemAttribute getAttribute (final int attributeID, final int unitsID)
+    {
+        //if ($assert.ENABLED) $assert.ASSERT ((attributeID & getMetadata ().getAttributeIDs ()) != 0, "invalid attribute ID [" + attributeID + "] for type [" + getMetadata ().getTypeID () + "]");
+        
+        if ((getMetadata ().getAttributeIDs () & (1 << attributeID)) == 0)
+            return null;
+        else
+            return IItemAttribute.Factory.getAttribute (attributeID, unitsID);
+    }
+    
+    public int getAggregate (final int type)
+    {
+        final int [] aggregates = m_aggregates;
+        int value = aggregates [type];
+        
+        if (value < 0)
+        {
+            // don't fault aggregate types all at once since there are
+            // plenty of exceptions to the additive roll up rule:
+            
+            value = 0;
+            for (Iterator children = m_children.iterator (); children.hasNext (); )
+            {
+                value += ((IItem) children.next ()).getAggregate (type);
+            }
+            aggregates [type] = value;
+            
+            return value;
+        }
+        
+        return value;
+    }
+
+    // protected: .............................................................
+    
+    
+    protected static final class ItemMetadata implements IItemMetadata
+    {
+        public int getTypeID ()
+        {
+            return m_typeID;
+        }
+        
+        public String getTypeName ()
+        {
+            return m_typeName;
+        }
+        
+        public long getAttributeIDs ()
+        {
+            return m_attributeIDs;
+        }
+        
+        ItemMetadata (final int typeID, final String typeName, final long attributeIDs)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (typeID >= TYPE_ID_ALL && typeID <= TYPE_ID_METHOD, "invalid type ID: " + typeID);
+            if ($assert.ENABLED) $assert.ASSERT (typeName != null, "typeName = null");
+            
+            
+            m_typeID = typeID;
+            m_typeName = typeName;
+            m_attributeIDs = attributeIDs;
+        }
+
+
+        private final int m_typeID;
+        private final String m_typeName;
+        private final long m_attributeIDs;
+        
+    } // end of nested class
+
+
+    protected void addChild (final IItem item)
+    {
+        if (item == null) throw new IllegalArgumentException ("null input: item");
+        
+        m_children.add (item);
+    }
+    
+    
+    protected final IItem m_parent;
+    protected final int [] m_aggregates;
+
+    // package: ...............................................................
+    
+
+    Item (final IItem parent)
+    {
+        m_parent = parent;
+        m_children = new ArrayList ();
+        
+        m_aggregates = new int [NUM_OF_AGGREGATES];
+        for (int i = 0; i < m_aggregates.length; ++ i) m_aggregates [i] = -1;
+    }
+    
+    // private: ...............................................................
+    
+    
+    private final List m_children;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/ItemComparator.java b/core/java12/com/vladium/emma/report/ItemComparator.java
new file mode 100644
index 0000000..ff33b1b
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/ItemComparator.java
@@ -0,0 +1,110 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ItemComparator.java,v 1.1.1.1 2004/05/09 16:57:37 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.Comparator;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface ItemComparator extends Comparator
+{
+    // public: ................................................................
+    
+    
+    ItemComparator NULL_COMPARATOR = new Factory.NullComparator ();
+    
+    
+    abstract class Factory
+    {
+        public static ItemComparator create (final int [] attributeIDsWithDir, final int unitsID)
+        {
+            if (attributeIDsWithDir == null)
+                throw new IllegalArgumentException ("null input: attributeIDsWithDir");
+            
+            if (attributeIDsWithDir.length == 0)
+                return NULL_COMPARATOR;
+                
+            // TODO: validate against duplicates
+            // TODO: memoize
+            
+            // TODO: move the code below into the attr factory
+            final Comparator [] comparators = new Comparator [attributeIDsWithDir.length >> 1];
+            for (int a = 0; a < attributeIDsWithDir.length; a += 2)
+            {
+                final int attributeID = attributeIDsWithDir [a];
+                
+                final Comparator comparator = IItemAttribute.Factory.getAttribute (attributeID, unitsID).comparator ();
+                comparators [a >> 1] = attributeIDsWithDir [a + 1] < 0 ? new ReverseComparator (comparator) : comparator;
+            }
+            
+            return new CompositeComparator (comparators);
+        }
+
+        private static final class NullComparator implements ItemComparator
+        {
+            public int compare (final Object l, final Object g)
+            {
+                return 0; 
+            }
+            
+        } // end of nested class
+        
+        
+        private static final class ReverseComparator implements ItemComparator
+        {
+            public int compare (final Object l, final Object g)
+            {
+                return m_comparator.compare (g, l); 
+            }
+
+            
+            ReverseComparator (final Comparator comparator)
+            {
+                m_comparator = comparator;
+            }
+            
+            
+            private final Comparator m_comparator;
+            
+        } // end of nested class
+        
+        
+        private static final class CompositeComparator implements ItemComparator
+        {
+            public int compare (final Object l, final Object g)
+            {
+                // TODO: this needs to check whether both items have a given attr type
+                
+                for (int c = 0; c < m_comparators.length; ++ c)
+                {
+                    final int diff = m_comparators [c].compare (l, g);
+                    if (diff != 0) return diff;
+                }
+                
+                return 0;
+            }
+
+            
+            CompositeComparator (final Comparator [] comparators)
+            {
+                m_comparators = comparators;
+            }
+            
+            
+            private final Comparator [] m_comparators;
+            
+        } // end of nested class
+        
+    } // end of nested interface 
+    
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/MethodItem.java b/core/java12/com/vladium/emma/report/MethodItem.java
new file mode 100644
index 0000000..0627b74
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/MethodItem.java
@@ -0,0 +1,218 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: MethodItem.java,v 1.1.1.1.2.1 2004/06/20 20:07:22 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import com.vladium.util.Descriptors;
+import com.vladium.util.IntObjectMap;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.data.IMetadataConstants;
+import com.vladium.emma.data.MethodDescriptor;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class MethodItem extends Item
+{
+    // public: ................................................................
+    
+    public MethodItem (final IItem parent, final int ID, final String name, final String descriptor, final int firstLine)
+    {
+        super (parent);
+        
+        m_ID = ID;
+        m_name = name;
+        m_descriptor = descriptor;
+        m_firstLine = firstLine;
+    }
+      
+    public String getName ()
+    {
+        if (m_userName == null)
+        {
+            m_userName = Descriptors.methodVMNameToJavaName (m_parent.getName (), m_name, m_descriptor, true, true, true);
+        }
+        
+        return m_userName;
+    }
+    
+    public int getID ()
+    {
+        return m_ID;
+    }
+    
+    public int getFirstLine ()
+    {
+        return m_firstLine;
+    }
+    
+    public int getAggregate (final int type)
+    {
+        final int [] aggregates = m_aggregates;
+        
+        int value = aggregates [type];
+        
+        if (value < 0)
+        {
+            final ClassItem parent = ((ClassItem) m_parent);
+            
+            final MethodDescriptor method = parent.m_cls.getMethods () [m_ID];
+            final int status = method.getStatus ();
+                    
+            if ((status & IMetadataConstants.METHOD_NO_BLOCK_DATA) != 0)
+            {
+                if ($assert.ENABLED) $assert.ASSERT (false, "excluded method in report data model");
+                
+                for (int i = 0; i < aggregates.length; ++ i) aggregates [i] = 0;
+            }
+            else
+            {
+                final boolean lineInfo = ((status & IMetadataConstants.METHOD_NO_LINE_NUMBER_TABLE) == 0);                
+                final boolean [] coverage = parent.m_coverage != null ? parent.m_coverage [m_ID] : null;  
+                              
+                final int totalBlockCount = method.getBlockCount ();
+
+                aggregates [TOTAL_METHOD_COUNT] = 1; // TODO: check that excluded methods are accounted for correctly                
+                aggregates [TOTAL_BLOCK_COUNT] = totalBlockCount;
+                                
+                int totalBlockInstr = 0;
+                
+                final int [] blockSizes = method.getBlockSizes ();
+                
+                if (coverage != null)
+                {
+                    int coverageBlockCount = 0, coverageLineCount = 0;
+                    int coverageBlockInstr = 0, coverageLineInstr = 0;
+
+                    for (int b = 0; b < totalBlockCount; ++ b)
+                    {
+                        final int instr = blockSizes [b];
+                         
+                        totalBlockInstr += instr;
+                        if (coverage [b])
+                        {
+                            ++ coverageBlockCount;
+                            coverageBlockInstr += instr;
+                        }
+                    }
+                    
+                    if (lineInfo)
+                    {
+                        final IntObjectMap lineMap = method.getLineMap (); // TODO: expensive way to get totalLineCount
+                        final int totalLineCount = lineMap.size ();
+                    
+                        aggregates [TOTAL_LINE_COUNT] = totalLineCount;
+                        
+                        final int [] lines = lineMap.keys ();
+                        for (int l = 0; l < totalLineCount; ++ l)
+                        {
+                            final int [] blocks = (int []) lineMap.get (lines [l]);
+                            
+                            int thisLineCoverageCount = 0; final int thisLineTotalCount = blocks.length;
+                            int thisLineCoverageInstr = 0, thisLineTotalInstr = 0;
+                            
+                            for (int bID = 0; bID < thisLineTotalCount; ++ bID)
+                            {
+                                final int b = blocks [bID];
+
+                                final int instr = blockSizes [b];
+                                
+                                thisLineTotalInstr += instr;
+                                if (coverage [b])
+                                {
+                                    ++ thisLineCoverageCount;
+                                    thisLineCoverageInstr += instr;
+                                }
+                            }
+                            
+                            coverageLineCount += (PRECISION * thisLineCoverageCount) / thisLineTotalCount;
+                            coverageLineInstr += (PRECISION * thisLineCoverageInstr) / thisLineTotalInstr;
+                        }
+                        
+                        aggregates [COVERAGE_LINE_COUNT] = coverageLineCount;
+                        aggregates [COVERAGE_LINE_INSTR] = coverageLineInstr;
+                    }
+
+                    aggregates [TOTAL_BLOCK_INSTR] = totalBlockInstr;
+                    aggregates [COVERAGE_METHOD_COUNT] = coverageBlockCount > 0 ? 1 : 0;                                        
+                    aggregates [COVERAGE_BLOCK_COUNT] = coverageBlockCount;
+                    aggregates [COVERAGE_BLOCK_INSTR] = coverageBlockInstr;
+                    
+                }
+                else
+                {
+                    for (int b = 0; b < totalBlockCount; ++ b)
+                    {
+                        totalBlockInstr += blockSizes [b];
+                    }
+                        
+                    aggregates [TOTAL_BLOCK_INSTR] = totalBlockInstr;
+                    aggregates [COVERAGE_METHOD_COUNT] = 0;
+                    aggregates [COVERAGE_BLOCK_COUNT] = 0;
+                    aggregates [COVERAGE_BLOCK_INSTR] = 0;
+                    
+                    if (lineInfo)
+                    {
+                        final IntObjectMap lineMap = method.getLineMap (); // TODO: expensive way to get totalLineCount
+                        final int totalLineCount = lineMap.size ();
+                    
+                        aggregates [TOTAL_LINE_COUNT] = totalLineCount;    
+                        aggregates [COVERAGE_LINE_COUNT] = 0;
+                        aggregates [COVERAGE_LINE_INSTR] = 0;
+                    }
+                }
+            }
+            
+            return aggregates [type];
+        }
+        
+        return value;
+    }
+       
+    public void accept (final IItemVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+
+    public final IItemMetadata getMetadata ()
+    {
+        return METADATA;
+    }
+    
+    public static IItemMetadata getTypeMetadata ()
+    {
+        return METADATA;
+    }
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+        
+    // private: ...............................................................
+    
+    
+    private final int m_ID;
+    private final String m_name, m_descriptor;
+    private final int m_firstLine;
+    private transient String m_userName;
+    
+    private static final Item.ItemMetadata METADATA; // set in <clinit>
+        
+    static
+    {
+        METADATA = new Item.ItemMetadata (IItemMetadata.TYPE_ID_METHOD, "method",
+            1 << IItemAttribute.ATTRIBUTE_NAME_ID |
+            1 << IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID);
+    }
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/PackageItem.java b/core/java12/com/vladium/emma/report/PackageItem.java
new file mode 100644
index 0000000..f6c7049
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/PackageItem.java
@@ -0,0 +1,76 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: PackageItem.java,v 1.1.1.1 2004/05/09 16:57:38 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class PackageItem extends Item
+{
+    // public: ................................................................
+    
+    public PackageItem (final IItem parent, final String name, final String VMname) // TODO: this is VM name for now
+    {
+        super (parent);
+        
+        m_name = name;
+        m_VMname = VMname;
+    }
+    
+    public String getName ()
+    {
+        return m_name;
+    }
+    
+    public String getVMName ()
+    {
+        return m_VMname;
+    }
+    
+    public void accept (final IItemVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public final IItemMetadata getMetadata ()
+    {
+        return METADATA;
+    }
+    
+    public static IItemMetadata getTypeMetadata ()
+    {
+        return METADATA;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final String m_name, m_VMname;
+    
+    private static final Item.ItemMetadata METADATA; // set in <clinit>
+        
+    static
+    {
+        METADATA = new Item.ItemMetadata (IItemMetadata.TYPE_ID_PACKAGE, "package",
+            1 << IItemAttribute.ATTRIBUTE_NAME_ID |
+            1 << IItemAttribute.ATTRIBUTE_CLASS_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID);
+    }
+
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/ReportDataModel.java b/core/java12/com/vladium/emma/report/ReportDataModel.java
new file mode 100644
index 0000000..4f94d75
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/ReportDataModel.java
@@ -0,0 +1,179 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportDataModel.java,v 1.1.1.1 2004/05/09 16:57:38 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vladium.util.Descriptors;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.ClassDescriptor;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.data.IMetadataConstants;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.MethodDescriptor;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+final class ReportDataModel implements IReportDataModel
+{
+    // public: ................................................................
+
+
+    public synchronized IReportDataView getView (final int viewType)
+    {
+        // TODO: merge the two branches together
+        
+        if (viewType >= m_views.length) throw new IllegalArgumentException ("invalid viewType: " + viewType);
+        
+        IReportDataView view = m_views [viewType];
+        
+        if (view != null)
+            return view;
+        else
+        {
+            final boolean srcView = viewType == IReportDataView.HIER_SRC_VIEW;
+            
+            if (srcView && ! m_mdata.hasSrcFileData ())
+                throw new IllegalStateException ("source file data view requested for metadata with incomplete SourceFile debug info");
+
+            final AllItem root = new AllItem ();
+            final Map /* String(pkg name) -> PackageItem */ packageMap = new HashMap ();
+            final Map /* String(pkg-prefixed src file name) -> ClassItem */ srcfileMap = new HashMap ();
+            
+            for (Iterator /* ClassDescriptor */ descriptors = m_mdata.iterator (); descriptors.hasNext (); )
+            {    
+                final ClassDescriptor cls = (ClassDescriptor) descriptors.next ();
+                String packageVMName = cls.getPackageVMName ();
+                
+                PackageItem packageItem = (PackageItem) packageMap.get (packageVMName);
+                if (packageItem == null)
+                {
+                    final String packageName = packageVMName.length () == 0 ? "default package" : Descriptors.vmNameToJavaName (packageVMName); 
+                    packageItem = new PackageItem (root, packageName, packageVMName);
+                    packageMap.put (packageVMName, packageItem);
+                    
+                    root.addChild (packageItem);
+                }
+                
+                SrcFileItem srcfileItem = null;
+                if (srcView)
+                {                
+                    final String srcFileName = cls.getSrcFileName ();
+                    if ($assert.ENABLED) $assert.ASSERT (srcFileName != null, "src file name = null");
+                    
+                    final String fullSrcFileName = Descriptors.combineVMName (packageVMName, srcFileName);
+                    
+                    srcfileItem = (SrcFileItem) srcfileMap.get (fullSrcFileName);
+                    if (srcfileItem == null)
+                    {
+                        srcfileItem = new SrcFileItem (packageItem, srcFileName, fullSrcFileName);
+                        srcfileMap.put (fullSrcFileName, srcfileItem);
+                        
+                        packageItem.addChild (srcfileItem);
+                    }
+                }
+                
+                final ICoverageData.DataHolder data = m_cdata.getCoverage (cls);
+                
+                // check metadata and coverage data consistency:
+                
+                if (data != null)
+                {
+                    if (data.m_stamp != cls.getStamp ())
+                        throw new EMMARuntimeException (IAppErrorCodes.CLASS_STAMP_MISMATCH,
+                                                        new Object [] { Descriptors.vmNameToJavaName (cls.getClassVMName ()) }); 
+                }
+                
+                final boolean [][] coverage = data != null ? data.m_coverage : null;
+                
+                if ($assert.ENABLED) $assert.ASSERT (! srcView || srcfileItem != null, "null srcfileItem");
+                
+                final ClassItem classItem = srcView ? new ClassItem (srcfileItem, cls, coverage) : new ClassItem (packageItem, cls, coverage);
+                final MethodDescriptor [] methods = cls.getMethods ();
+                
+                // TODO: handle edge case when all methods of a class have METHOD_NO_BLOCK_DATA set
+                for (int m = 0; m < methods.length; ++ m)
+                {
+                    final MethodDescriptor method = methods [m];
+                        
+                    if ((method.getStatus () & IMetadataConstants.METHOD_NO_BLOCK_DATA) != 0) continue;
+                    
+                    // TODO: wouldn't it be more consistent to simply pass the entire descriptor into MethodItems? (eval mem savings)
+                    final MethodItem methodItem = new MethodItem (classItem, m, method.getName (), method.getDescriptor (), method.getFirstLine ());                    
+                    // TODO: need to fold class's name into a method name prefix for collapsing case [only when it is not the same as the file name]
+                    
+                    classItem.addChild (methodItem);
+                }
+                
+                if (srcView)
+                    srcfileItem.addChild (classItem);
+                else
+                    packageItem.addChild (classItem);
+            }
+            
+            view = new ReportDataView (root);
+            
+            m_views [viewType] = view;
+            return view;
+        }
+    }
+
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    
+    ReportDataModel (final IMetaData mdata, final ICoverageData cdata)
+    {
+        if (mdata == null) throw new IllegalArgumentException ("null input: mdata");
+        if (cdata == null) throw new IllegalArgumentException ("null input: cdata");
+        
+        m_views = new IReportDataView [2];
+        
+        // TODO: report generators work off data model views only; I should deref
+        // mdata and cdata as soon as all possible views have been constructed and cached
+        
+        m_mdata = mdata;
+        m_cdata = cdata;
+    }
+    
+    // private: ...............................................................
+    
+    
+    private static final class ReportDataView implements IReportDataView
+    {
+        public IItem getRoot()
+        {
+            return m_root;
+        }
+        
+        ReportDataView (final IItem root)
+        {
+            m_root = root;
+        }
+        
+        
+        private final IItem m_root;
+        
+    } // end of nested class
+    
+    
+    private final IMetaData m_mdata;
+    private final ICoverageData m_cdata;
+    
+    private final IReportDataView [] m_views;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/ReportProcessor.java b/core/java12/com/vladium/emma/report/ReportProcessor.java
new file mode 100644
index 0000000..04fb09a
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/ReportProcessor.java
@@ -0,0 +1,341 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportProcessor.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.Files;
+import com.vladium.util.IConstants;
+import com.vladium.util.IProperties;
+import com.vladium.util.Strings;
+import com.vladium.util.asserts.$assert;
+import com.vladium.util.exception.Exceptions;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.Processor;
+import com.vladium.emma.data.DataFactory;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.IMergeable;
+import com.vladium.emma.data.IMetaData;
+
+// ----------------------------------------------------------------------------
+/*
+ * This class was not meant to be public by design. It is made to to work around
+ * access bugs in reflective invocations. 
+ */
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ReportProcessor extends Processor
+                            implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    public static ReportProcessor create ()
+    {
+        return new ReportProcessor ();
+    }
+    
+    /**
+     * 
+     * @param path [null is equivalent to an empty array]
+     */
+    public synchronized final void setDataPath (final String [] path)
+    {
+        if ((path == null) || (path.length == 0))
+            m_dataPath = IConstants.EMPTY_FILE_ARRAY;
+        else
+            m_dataPath = Files.pathToFiles (path, true);
+    }
+    
+    /**
+     * @param path [null is equivalent to no source path]
+     */
+    public synchronized void setSourcePath (final String [] path)
+    {
+        if (path == null)
+            m_sourcePath = null;
+        else
+            m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path
+    }
+
+    /**
+     * 
+     * @param types [may not be null]
+     */
+    public synchronized void setReportTypes (final String [] types)
+    {
+        if (types == null) throw new IllegalArgumentException ("null input: types");
+        
+        final String [] reportTypes = Strings.removeDuplicates (types, true);
+        if (reportTypes.length == 0) throw new IllegalArgumentException ("empty input: types");
+        
+        if ($assert.ENABLED) $assert.ASSERT (reportTypes != null && reportTypes.length  > 0);
+        
+        
+        final IReportGenerator [] reportGenerators = new IReportGenerator [reportTypes.length];
+        for (int t = 0; t < reportTypes.length; ++ t)
+        {
+            reportGenerators [t] = AbstractReportGenerator.create (reportTypes [t]);
+        }
+        
+        m_reportGenerators = reportGenerators;
+    }
+    
+    // protected: .............................................................
+
+    
+    protected void validateState ()
+    {
+        super.validateState ();
+        
+        if (m_dataPath == null)
+            throw new IllegalStateException ("data path not set");
+            
+        // [m_sourcePath can be null]
+        
+        if ((m_reportGenerators == null) || (m_reportGenerators.length == 0))
+            throw new IllegalStateException ("report types not set");
+        
+        // [m_propertyOverrides can be null]
+    }
+
+    
+    protected void _run (final IProperties toolProperties)
+    {
+        final Logger log = m_log;
+        
+        final boolean verbose = m_log.atVERBOSE ();
+        if (verbose)
+        {
+            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
+            
+            // [assertion: m_dataPath != null]
+            log.verbose ("input data path:");
+            log.verbose ("{");
+            for (int p = 0; p < m_dataPath.length; ++ p)
+            {
+                final File f = m_dataPath [p];
+                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
+                
+                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
+            }
+            log.verbose ("}");
+            
+            
+            if ((m_sourcePath == null) || (m_sourcePath.length == 0))
+            {
+                log.verbose ("source path not set");
+            }
+            else
+            {
+                log.verbose ("source path:");
+                log.verbose ("{");
+                for (int p = 0; p < m_sourcePath.length; ++ p)
+                {
+                    final File f = m_sourcePath [p];
+                    final String nonexistent = f.exists () ? "" : "{nonexistent} ";
+                    
+                    log.verbose ("  " + nonexistent + f.getAbsolutePath ());
+                }
+                log.verbose ("}");
+            }
+        }
+        else
+        {
+            log.info ("processing input files ...");
+        }
+        
+        RuntimeException failure = null;
+        try
+        {
+            final long start = log.atINFO () ? System.currentTimeMillis () : 0;
+            
+            IMetaData mdata = null;
+            ICoverageData cdata = null;
+            
+            // merge all data files:
+            try
+            {
+                for (int f = 0; f < m_dataPath.length; ++ f)
+                {
+                    final File dataFile = m_dataPath [f];
+                    if (verbose) log.verbose ("processing input file [" + dataFile.getAbsolutePath () + "] ...");
+                    
+                    final IMergeable [] fileData = DataFactory.load (dataFile);
+                    
+                    final IMetaData _mdata = (IMetaData) fileData [DataFactory.TYPE_METADATA];
+                    if (_mdata != null)
+                    {
+                        if (verbose) log.verbose ("  loaded " + _mdata.size () + " metadata entries");
+                        
+                        if (mdata == null)
+                            mdata = _mdata;
+                        else
+                            mdata = (IMetaData) mdata.merge (_mdata); // note: later datapath entries override earlier ones
+                    }
+                    
+                    final ICoverageData _cdata = (ICoverageData) fileData [DataFactory.TYPE_COVERAGEDATA];
+                    if (_cdata != null)
+                    {
+                        if (verbose) log.verbose ("  loaded " + _cdata.size () + " coverage data entries");
+                        
+                        if (cdata == null)
+                            cdata = _cdata;
+                        else
+                            cdata = (ICoverageData) cdata.merge (_cdata); // note: later datapath entries override earlier ones
+                    }
+                    
+                    ++ m_dataFileCount;
+                }
+                
+                if (log.atINFO ())
+                {
+                    final long end = System.currentTimeMillis ();
+                    
+                    log.info (m_dataFileCount + " file(s) read and merged in " + (end - start) + " ms");
+                }
+                
+                if ((mdata == null) || mdata.isEmpty ())
+                {
+                    log.warning ("nothing to do: no metadata found in any of the data files");
+                    
+                    return;
+                }
+                
+                if (cdata == null)
+                {
+                    log.warning ("nothing to do: no runtime coverage data found in any of the data files");
+                    
+                    return;
+                }
+                
+                if (cdata.isEmpty ())
+                {
+                    log.warning ("no collected coverage data found in any of the data files [all reports will be empty]");
+                }
+                
+                
+                if (verbose)
+                {
+                    if (mdata != null)
+                    {
+                        log.verbose ("  merged metadata contains " + mdata.size () + " entries");
+                    }
+                    
+                    if (cdata != null)
+                    {
+                        log.verbose ("  merged coverage data contains " + cdata.size () + " entries");
+                    }
+                }
+                                
+                SourcePathCache srcpathCache = null;
+                if (m_sourcePath != null) srcpathCache = new SourcePathCache (m_sourcePath, true); // ignore non-existent source dirs 
+                
+                for (int g = 0; g < m_reportGenerators.length; ++ g)
+                {
+                    final IReportGenerator generator = m_reportGenerators [g];
+                    
+                    try
+                    {
+                        // no shallow copies of 'mdata' or 'cdata' are needed here
+                        // because this command never runs in a concurrent situation
+                        
+                        generator.process (mdata, cdata, srcpathCache, toolProperties);
+                    }
+                    catch (Throwable t)
+                    {
+                        // TODO: handle and continue
+                        t.printStackTrace (System.out);
+                        
+                        // TODO: continue here
+                        break;
+                    }
+                    finally
+                    {
+                        try { generator.cleanup (); } catch (Throwable ignore) {}
+                    }
+                }
+            }
+            catch (IOException ioe)
+            {
+                // TODO: handle
+                ioe.printStackTrace (System.out);
+            }
+        }
+        catch (SecurityException se)
+        {
+            failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
+        }
+        catch (RuntimeException re)
+        {
+            failure = re;
+        }
+        finally
+        {
+            reset ();
+        }
+        
+        if (failure != null)
+        {
+            if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES))
+            {
+                throw new EMMARuntimeException (UNEXPECTED_FAILURE,
+                                                new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK},
+                                                failure);
+            }
+            else
+                throw failure;
+        }
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private ReportProcessor ()
+    {
+        m_dataPath = IConstants.EMPTY_FILE_ARRAY;
+    }
+    
+    private void reset ()
+    {
+        m_dataFileCount = 0;
+    }
+    
+    
+    // caller-settable state [scoped to this runner instance]:
+    
+    private File [] m_dataPath;     // required to be non-null for run()
+    private File [] m_sourcePath;   // can be null/empty for run()
+    private IReportGenerator [] m_reportGenerators; // required to be non-null for run()
+
+    // internal run()-scoped state:
+    
+    private int m_dataFileCount;    
+    
+    private static final Class [] EXPECTED_FAILURES; // set in <clinit>
+    
+    static
+    {
+        EXPECTED_FAILURES = new Class []
+        {
+            EMMARuntimeException.class,
+            IllegalArgumentException.class,
+            IllegalStateException.class,
+        };
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/ReportProperties.java b/core/java12/com/vladium/emma/report/ReportProperties.java
new file mode 100644
index 0000000..940ca36
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/ReportProperties.java
@@ -0,0 +1,576 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportProperties.java,v 1.1.1.1 2004/05/09 16:57:38 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import com.vladium.util.Files;
+import com.vladium.util.IProperties;
+import com.vladium.util.IntIntMap;
+import com.vladium.util.IntVector;
+import com.vladium.util.ObjectIntMap;
+import com.vladium.util.Property;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class ReportProperties implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    
+    public static final IProperties.IMapper REPORT_PROPERTY_MAPPER; // set in <clinit>
+    
+       
+    public static final class ParsedProperties
+    {
+        public void setOutEncoding (final String outEncoding)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (outEncoding != null, "null input: outEncoding");
+            
+            m_outEncoding = outEncoding;
+        }
+
+        public String getOutEncoding ()
+        {
+            return m_outEncoding;
+        }
+
+        public void setOutDir (final File outDir)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (outDir != null, "null input: outDir");
+            
+            m_outDir = outDir;
+        }
+
+        public File getOutDir ()
+        {
+            return m_outDir;
+        }
+
+        public void setOutFile (final File outFile)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (outFile != null, "null input: outFile");
+            
+            m_outFile = outFile;
+        }
+
+        public File getOutFile ()
+        {
+            return m_outFile;
+        }
+
+        public void setUnitsType (final int unitsType)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (unitsType >= IItemAttribute.UNITS_COUNT && unitsType <= IItemAttribute.UNITS_INSTR, "invalid units type: " + unitsType);
+            
+            m_unitsType = unitsType;
+        }
+
+        public int getUnitsType ()
+        {
+            return m_unitsType;
+        }
+
+        public void setViewType (final int viewType)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (viewType >= IReportDataView.HIER_CLS_VIEW && viewType <= IReportDataView.HIER_SRC_VIEW, "invalid view type: " + viewType);
+            
+            m_viewType = viewType;
+        }
+
+        public int getViewType ()
+        {
+            return m_viewType;
+        }
+
+        public void setDepth (final int depth)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (depth >= IItemMetadata.TYPE_ID_ALL && depth <= IItemMetadata.TYPE_ID_METHOD, "invalid depth: " + depth);
+            
+            m_depth = depth;
+        }
+
+        public int getDepth()
+        {
+            return m_depth;
+        }
+
+        public void setHideClasses (final boolean hideClasses)
+        {
+            m_hideClasses = hideClasses;
+        }
+
+        public boolean getHideClasses ()
+        {
+            return m_hideClasses;
+        }
+
+        public void setColumnOrder (final int [] columnOrder)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (columnOrder != null && columnOrder.length != 0, "null/empty input: outEncoding");
+            
+            m_columnOrder = columnOrder;
+        }
+
+        public int [] getColumnOrder ()
+        {
+            return m_columnOrder;
+        }
+
+        public void setSortOrder (final int [] sortOrder)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (sortOrder != null, "null input: sortOrder");
+            
+            m_sortOrder = sortOrder;
+        }
+
+        public int [] getSortOrder ()
+        {
+            return m_sortOrder;
+        }
+
+        public void setMetrics (final IntIntMap metrics)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (metrics != null, "null input: metrics");
+            
+            m_metrics = metrics;
+        }
+
+        public IntIntMap getMetrics ()
+        {
+            return m_metrics;
+        }
+        
+        // TODO: toString/logging
+        
+        void validate () throws IllegalArgumentException
+        {
+            if ($assert.ENABLED)
+            {
+                $assert.ASSERT (m_outEncoding != null, "m_outEncoding not set");
+                $assert.ASSERT (m_outDir != null || m_outFile != null, "either m_outDir or m_outFile must be set");
+                $assert.ASSERT (m_columnOrder != null, "m_columnOrder not set");
+                $assert.ASSERT (m_sortOrder != null, "m_sortOrder not set");
+                $assert.ASSERT (m_metrics != null, "m_metrics not set");
+            }
+        }
+
+
+        private String m_outEncoding;
+        private File m_outDir;
+        private File m_outFile;
+        
+        private int m_unitsType;
+        private int m_viewType;
+        
+        private boolean m_hideClasses;
+        private int m_depth;
+        
+        // TODO: fraction/number format strings...
+        
+        private int [] m_columnOrder; // attribute IDs [order indicates column order] 
+        private int [] m_sortOrder; // if m_sortOrder[i+1]>0 , sort m_columnOrder[m_sortOrder[i]] in ascending order
+        private IntIntMap m_metrics; // pass criteria (column attribute ID -> metric)
+        
+    } // end of nested class
+    
+    
+//    /**
+//     * Creates a property view specific to 'reportType' report type.
+//     * 
+//     * @param appProperties
+//     * @param reportType
+//     * @return
+//     */
+//    public static Properties getReportProperties (final Properties appProperties, final String reportType)
+//    {
+//        if ((reportType == null) || (reportType.length () == 0))
+//             throw new IllegalArgumentException ("null/empty input: reportType");
+//
+//        if (appProperties == null) return new XProperties ();
+//             
+//        return new ReportPropertyLookup (appProperties, reportType);
+//    }
+    
+    
+//    /**
+//     * @param type [null/empty indicates type-neutral property]
+//     */
+//    public static String getReportProperty (final String type, final Map properties, final String key)
+//    {
+//        if (properties == null) throw new IllegalArgumentException ("null input: properties");
+//        if (key == null) throw new IllegalArgumentException ("null input: key");
+//        
+//        String fullKey;
+//        
+//        if ((type == null) || (type.length () == 0))
+//            fullKey = IReportParameters.PREFIX.concat (key);
+//        else
+//        {
+//            fullKey = IReportParameters.PREFIX.concat (type).concat (".").concat (key);
+//            
+//            if (! properties.containsKey (fullKey)) // default to type-neutral lookup
+//                fullKey = IReportParameters.PREFIX.concat (key);
+//        }
+//            
+//        return (String) properties.get (fullKey);        
+//    }
+//    
+//    public static String getReportParameter (final String type, final Map properties, final String key, final String def)
+//    {
+//        final String value = getReportProperty (type, properties, key);
+//        
+//        return (value == null) ? def : value; 
+//    }
+
+    
+    public static ParsedProperties parseProperties (final IProperties properties, final String type)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (properties != null, "properties = null");
+        
+        final ParsedProperties result = new ParsedProperties ();
+        {
+            result.setOutEncoding (getReportProperty (properties, type, IReportProperties.OUT_ENCODING, false));
+        }
+        
+        // TODO: outDirName is no longer supported
+        
+        {
+            final String outDirName = getReportProperty (properties, type, IReportProperties.OUT_DIR, true);
+            final String outFileName = getReportProperty (properties, type, IReportProperties.OUT_FILE, false);
+    
+            // renormalize the out dir and file combination:
+        
+            if (outFileName != null)
+            {
+                final File fullOutFile = Files.newFile (outDirName, outFileName);
+                
+                final File dir = fullOutFile.getParentFile ();
+                if (dir != null) result.setOutDir (dir);
+            
+                result.setOutFile (new File (fullOutFile.getName ()));
+            }
+            else if (outDirName != null)
+            {
+                result.setOutDir (new File (outDirName));
+            }
+        }
+
+        {
+            final String unitsType = getReportProperty (properties, type, IReportProperties.UNITS_TYPE, true, IReportProperties.DEFAULT_UNITS_TYPE);
+            result.setUnitsType (IReportProperties.COUNT_UNITS.equals (unitsType) ? IItemAttribute.UNITS_COUNT : IItemAttribute.UNITS_INSTR);
+            
+            // TODO: invalid setting not checked
+        }
+        {
+            /* view type is no longer a user-overridable property [it is driven by SourceFile attribute presence]
+
+            final String viewType = getReportProperty (properties, type, IReportProperties.VIEW_TYPE, IReportProperties.DEFAULT_VIEW_TYPE);
+            result.setViewType (IReportProperties.SRC_VIEW.equals (viewType) ? IReportDataView.HIER_SRC_VIEW : IReportDataView.HIER_CLS_VIEW);
+            */
+            
+            result.setViewType (IReportDataView.HIER_SRC_VIEW);
+        } 
+        
+        {
+            final String hideClasses = getReportProperty (properties, type, IReportProperties.HIDE_CLASSES, true, IReportProperties.DEFAULT_HIDE_CLASSES);
+            result.setHideClasses (Property.toBoolean (hideClasses));
+        
+            // TODO: log this
+            if (result.getViewType () == IReportDataView.HIER_CLS_VIEW)
+                result.setHideClasses (false);
+        }
+        {
+            final String depth = getReportProperty (properties, type, IReportProperties.DEPTH, false, IReportProperties.DEFAULT_DEPTH);
+            
+            if (IReportProperties.DEPTH_ALL.equals (depth))
+                result.setDepth (AllItem.getTypeMetadata ().getTypeID ());
+            else if (IReportProperties.DEPTH_PACKAGE.equals (depth))
+                result.setDepth (PackageItem.getTypeMetadata ().getTypeID ());
+            else if (IReportProperties.DEPTH_SRCFILE.equals (depth))
+                result.setDepth (SrcFileItem.getTypeMetadata ().getTypeID ());
+            else if (IReportProperties.DEPTH_CLASS.equals (depth))
+                result.setDepth (ClassItem.getTypeMetadata ().getTypeID ());
+            else if (IReportProperties.DEPTH_METHOD.equals (depth))
+                result.setDepth (MethodItem.getTypeMetadata ().getTypeID ());
+            else
+                // TODO: properly prefixes prop name
+                throw new EMMARuntimeException (INVALID_PARAMETER_VALUE, new Object [] {IReportProperties.DEPTH, depth});
+        }
+        
+        if (result.getHideClasses () &&
+           (result.getViewType () == IReportDataView.HIER_SRC_VIEW) &&
+           (result.getDepth () == IItemMetadata.TYPE_ID_CLASS))
+        {
+            result.setDepth (IItemMetadata.TYPE_ID_SRCFILE);
+        }
+        
+        final Set /* String */ columnNames = new HashSet ();
+        {
+            final String columnList = getReportProperty (properties, type, IReportProperties.COLUMNS, false, IReportProperties.DEFAULT_COLUMNS);
+            final IntVector _columns = new IntVector ();
+            
+            final int [] out = new int [1];
+            
+            for (StringTokenizer tokenizer = new StringTokenizer (columnList, ","); tokenizer.hasMoreTokens (); )
+            {
+                final String columnName = tokenizer.nextToken ().trim ();
+                if (! COLUMNS.get (columnName, out))
+                {
+                    // TODO: generate the entire enum list in the err msg
+                    throw new EMMARuntimeException (INVALID_COLUMN_NAME, new Object [] {columnName});
+                }
+                
+                if (! REMOVE_DUPLICATE_COLUMNS || ! columnNames.contains (columnName))
+                {
+                    columnNames.add (columnName);
+                    _columns.add (out [0]);
+                }
+            }
+            
+            result.setColumnOrder (_columns.values ());
+        }
+        // [assertion: columnNames contains all columns for the report (some
+        // may get removed later by individual report generators if some debug info
+        // is missing)]
+        
+        {
+            final String sortList = getReportProperty (properties, type, IReportProperties.SORT, false, IReportProperties.DEFAULT_SORT);
+            final IntVector _sort = new IntVector ();
+            
+            final int [] out = new int [1];
+            
+            for (StringTokenizer tokenizer = new StringTokenizer (sortList, ","); tokenizer.hasMoreTokens (); )
+            {
+                final String sortSpec = tokenizer.nextToken ().trim ();
+                final String columnName;
+                final int dir;
+                
+                switch (sortSpec.charAt (0))
+                {
+                    case IReportProperties.ASC:
+                    {
+                        dir = +1;
+                        columnName = sortSpec.substring (1);
+                    }
+                    break;
+                    
+                    case IReportProperties.DESC:
+                    {
+                        dir = -1;
+                        columnName = sortSpec.substring (1);
+                    }
+                    break;
+                    
+                    default:
+                    {
+                        dir = +1;
+                        columnName = sortSpec;
+                    }
+                    break;
+                    
+                } // end of switch
+                
+                // silently ignore columns not in the column list:
+                if (columnNames.contains (columnName))
+                {
+                    COLUMNS.get (columnName, out);
+                    
+                    _sort.add (out [0]);    // sort attribute ID
+                    _sort.add (dir);        // sort direction
+                }
+                
+                result.setSortOrder (_sort.values ());
+            }
+        }
+        {
+            final String metricList = getReportProperty (properties, type, IReportProperties.METRICS, true, IReportProperties.DEFAULT_METRICS);
+            final IntIntMap _metrics = new IntIntMap ();
+            
+            final int [] out = new int [1];
+            
+            // TODO: perhaps should throw on invalid input here
+            for (StringTokenizer tokenizer = new StringTokenizer (metricList, ","); tokenizer.hasMoreTokens (); )
+            {
+                final String metricSpec = tokenizer.nextToken ().trim ();
+                final String columnName;
+                final double criterion;
+                
+                final int separator = metricSpec.indexOf (IReportProperties.MSEPARATOR);
+                if (separator > 0) // silently ignore invalid entries
+                {
+                    // silently ignore invalid cutoff values:
+                    try
+                    {
+                        criterion = Double.parseDouble (metricSpec.substring (separator + 1));
+                        if ((criterion < 0.0) || (criterion > 101.0)) continue;
+                    }
+                    catch (NumberFormatException nfe)
+                    {
+                        nfe.printStackTrace (System.out);
+                        continue;
+                    }
+                    
+                    columnName = metricSpec.substring (0, separator);
+                    
+                    // silently ignore columns not in the column list:
+                    if (columnNames.contains (columnName))
+                    {
+                        COLUMNS.get (columnName, out);
+                    
+                        _metrics.put (out [0], (int) Math.round (((criterion * IItem.PRECISION) / 100.0)));
+                    }
+                }
+            }
+                                        
+            result.setMetrics (_metrics);
+        }
+        
+        result.validate ();
+        
+        return result;
+    }
+
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class ReportPropertyMapper implements IProperties.IMapper
+    {
+        public String getMappedKey (final String key)
+        {
+            if (key != null)
+            {
+                if (key.startsWith (IReportProperties.PREFIX))
+                {
+                    final int secondDot = key.indexOf ('.', IReportProperties.PREFIX.length ());
+                    if (secondDot > 0) 
+                    {
+                        // TODO: make this more precise (actually check the report type value string)
+                        
+                        return IReportProperties.PREFIX.concat (key.substring (secondDot + 1));
+                    }
+                }
+            }
+            
+            return null;
+        }
+
+    } // end of nested class
+    
+    
+//    private static final class ReportPropertyLookup extends XProperties
+//    {
+//        // note: due to incredibly stupid coding in java.util.Properties
+//        // (getProperty() uses a non-virtual call to super.get(), while propertyNames()
+//        // uses a virtual call to the same method instead of delegating to getProperty())
+//        // I must override both methods below
+//        
+//        public String getProperty (String key)
+//        {
+//            return (String) get (key);
+//        }
+//        
+//        // TODO: this kind of lookup makes the property listing confusing
+//        
+//        public Object get (final Object _key)
+//        {
+//            if (! (_key instanceof String)) return null;
+//            
+//            String key = (String) _key;
+//            
+//            if (key.startsWith (IReportProperties.PREFIX))
+//                key = key.substring (IReportProperties.PREFIX.length ());
+//            
+//            if (key.startsWith (m_reportType))
+//                key = key.substring (m_reportType.length () + 1);
+//             
+//            String fullKey = IReportProperties.PREFIX.concat (m_reportType).concat (".").concat (key);
+//            
+//            String result = defaults.getProperty (fullKey, null);
+//            if (result != null) return result;
+//            
+//            // fall back to report type-neutral namespace:
+//            fullKey = IReportProperties.PREFIX.concat (key);
+//            
+//            result = defaults.getProperty (fullKey, null);
+//            if (result != null) return result;
+//            
+//            return null;
+//        }
+//        
+//        
+//        ReportPropertyLookup (final Properties appProperties, final String reportType)
+//        {
+//            super (appProperties);
+//                 
+//            m_reportType = reportType;
+//        }
+//
+//        
+//        private final String m_reportType; // never null or empty [factory-ensured]
+//        
+//    } // end of nested class
+    
+    
+    private ReportProperties () {} // prevent subclassing
+    
+    
+    private static String getReportProperty (final IProperties properties, final String type, final String key, final boolean allowBlank)
+    {
+        return getReportProperty (properties, type, key, allowBlank, null);
+    }
+    
+    private static String getReportProperty (final IProperties properties, final String type, final String key, final boolean allowBlank, final String dflt)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (properties != null, "null input: properties");
+        if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
+        
+        final String result = properties.getProperty (IReportProperties.PREFIX.concat (type).concat (".").concat (key), dflt);
+        
+        if (! allowBlank && (result != null) && (result.trim ().length () == 0))
+            return dflt;
+        else
+            return result;
+    }
+
+    
+    private static final boolean REMOVE_DUPLICATE_COLUMNS = true;
+    private static final ObjectIntMap /* col name:String -> metadata:IItemMetadata */ COLUMNS; // set in <clinit>
+    
+    static
+    {
+        REPORT_PROPERTY_MAPPER = new ReportPropertyMapper ();
+        
+        final ObjectIntMap columns = new ObjectIntMap ();
+        
+        columns.put (IReportProperties.ITEM_NAME_COLUMN, IItemAttribute.ATTRIBUTE_NAME_ID);
+        columns.put (IReportProperties.CLASS_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_CLASS_COVERAGE_ID);
+        columns.put (IReportProperties.METHOD_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID);
+        columns.put (IReportProperties.BLOCK_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID);
+        columns.put (IReportProperties.LINE_COVERAGE_COLUMN, IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID);
+        
+        COLUMNS = columns;
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/SourcePathCache.java b/core/java12/com/vladium/emma/report/SourcePathCache.java
new file mode 100644
index 0000000..9d197ae
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/SourcePathCache.java
@@ -0,0 +1,228 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SourcePathCache.java,v 1.1.1.1 2004/05/09 16:57:39 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class SourcePathCache
+{
+    // public: ................................................................
+    
+    // TODO: use soft cache for m_packageCache?
+    
+    /**
+     * @param sourcepath [can be empty]
+     */
+    public SourcePathCache (final String [] sourcepath, final boolean removeNonExistent)
+    {
+        if (sourcepath == null) throw new IllegalArgumentException ("null input: sourcepath");
+        
+        final List _sourcepath = new ArrayList (sourcepath.length);
+        for (int i = 0; i < sourcepath.length; ++ i)
+        {
+            final File dir = new File (sourcepath [i]);
+            
+            if (! removeNonExistent || (dir.isDirectory () && dir.exists ()))
+                _sourcepath.add (dir);
+        }
+        
+        m_sourcepath = new File [_sourcepath.size ()];
+        _sourcepath.toArray (m_sourcepath);
+        
+        m_packageCache = new HashMap ();
+    }
+    
+    /**
+     * @param sourcepath [can be empty]
+     */
+    public SourcePathCache (final File [] sourcepath, final boolean removeNonExistent)
+    {
+        if (sourcepath == null) throw new IllegalArgumentException ("null input: sourcepath");
+        
+        final List _sourcepath = new ArrayList (sourcepath.length);
+        for (int i = 0; i < sourcepath.length; ++ i)
+        {
+            final File dir = sourcepath [i];
+            
+            if (! removeNonExistent || (dir.isDirectory () && dir.exists ()))
+                _sourcepath.add (dir);
+        }
+        
+        m_sourcepath = new File [_sourcepath.size ()];
+        _sourcepath.toArray (m_sourcepath);
+        
+        m_packageCache = new HashMap ();
+    }
+    
+    /**
+     * @return absolute pathname [null if 'name' was not found in cache]
+     */
+    public synchronized File find (final String packageVMName, final String name)
+    {
+        if (packageVMName == null) throw new IllegalArgumentException ("null input: packageVMName");
+        if (name == null) throw new IllegalArgumentException ("null input: name");
+        
+        if (m_sourcepath.length == 0) return null;
+        
+        CacheEntry entry = (CacheEntry) m_packageCache.get (packageVMName);
+        
+        if (entry == null)
+        {
+            entry = new CacheEntry (m_sourcepath.length);
+            m_packageCache.put (packageVMName, entry);
+        }
+        
+        final Set [] listings = entry.m_listings;
+        for (int p = 0; p < listings.length; ++ p)
+        {
+            Set listing = listings [p];
+            if (listing == null)
+            {
+                listing = faultListing (m_sourcepath [p], packageVMName);
+                listings [p] = listing;
+            }
+            
+            // TODO: this is case-sensitive at this point
+            if (listing.contains (name))
+            {
+                final File relativeFile = new File (packageVMName.replace ('/', File.separatorChar), name);
+                
+                return new File (m_sourcepath [p], relativeFile.getPath ()).getAbsoluteFile ();
+            }
+        }
+        
+        return null;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class CacheEntry
+    {
+        CacheEntry (final int size)
+        {
+            m_listings = new Set [size];
+        }
+        
+        
+        final Set /* String */ [] m_listings;
+        
+    } // end of nested class
+    
+    
+    // NOTE: because java.io.* implements file filtering in bytecode
+    // there is no real perf advantage in using a filter here (I might
+    // as well do list() and filter the result myself 
+    
+    private static final class FileExtensionFilter implements FileFilter
+    {
+        public boolean accept (final File file)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (file != null, "file = null");
+            
+            if (file.isDirectory ()) return false; // filter out directories
+
+            final String name = file.getName ();
+            final int lastDot = name.lastIndexOf ('.');
+            if (lastDot <= 0) return false;
+            
+            // [assertion: lastDot > 0]
+            
+            // note: match is case sensitive
+            return m_extension.equals (name.substring (lastDot));
+        }
+        
+        public String toString ()
+        {
+            return super.toString () + ", extension = [" + m_extension + "]";
+        }
+        
+        FileExtensionFilter (final String extension)
+        {
+            if (extension == null)
+                throw new IllegalArgumentException ("null input: extension");
+            
+            // ensure starting '.':
+            final String canonical = canonicalizeExtension (extension); 
+            
+            if (extension.length () <= 1)
+                throw new IllegalArgumentException ("empty input: extension");
+            
+            m_extension = canonical;
+        }
+        
+        private static String canonicalizeExtension (final String extension)
+        {
+            if (extension.charAt (0) != '.')
+                return ".".concat (extension);
+            else
+                return extension;
+        }
+        
+        
+        private final String m_extension;
+        
+    } // end of nested class
+    
+    
+    private Set /* String */ faultListing (final File dir, final String packageVMName)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (dir != null, "dir = null");
+        if ($assert.ENABLED) $assert.ASSERT (packageVMName != null, "packageVMName = null");
+        
+        final File packageFile = new File (dir, packageVMName.replace ('/', File.separatorChar));
+        
+        final File [] listing = packageFile.listFiles (FILE_EXTENSION_FILTER);
+        
+        if ((listing == null) || (listing.length == 0))
+            return Collections.EMPTY_SET;
+        else
+        {
+            final Set result = new HashSet (listing.length);
+            for (int f = 0; f < listing.length; ++ f)
+            {
+                result.add (listing [f].getName ());
+            }
+            
+            return result;
+        }
+    }
+    
+
+    private final File [] m_sourcepath; // never null
+    private final Map /* packageVMName:String->CacheEntry */ m_packageCache; // never null
+    
+    private static final FileExtensionFilter FILE_EXTENSION_FILTER; // set in <clinit>
+    
+    static
+    {
+        FILE_EXTENSION_FILTER = new FileExtensionFilter (".java");
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/SrcFileItem.java b/core/java12/com/vladium/emma/report/SrcFileItem.java
new file mode 100644
index 0000000..4b06847
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/SrcFileItem.java
@@ -0,0 +1,303 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SrcFileItem.java,v 1.1.1.1.2.1 2004/06/20 20:07:22 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.util.Iterator;
+
+import com.vladium.util.IntObjectMap;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.data.ClassDescriptor;
+import com.vladium.emma.data.MethodDescriptor;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class SrcFileItem extends Item
+{
+    // public: ................................................................
+    
+    
+    public final class LineCoverageData
+    {
+        public static final int LINE_COVERAGE_ZERO = 0;
+        public static final int LINE_COVERAGE_PARTIAL = 1;
+        public static final int LINE_COVERAGE_COMPLETE = 2;
+        
+        public final int m_coverageStatus;
+        public final int [/* units mode */][/* total, coverage */] m_coverageRatio; // not null in LINE_COVERAGE_PARTIAL status only]
+        
+        LineCoverageData (final int coverageStatus, final int [][] coverageRatio)
+        {
+            m_coverageStatus = coverageStatus;
+            m_coverageRatio = coverageRatio;
+        }
+        
+    } // end of nested class
+    
+
+    public SrcFileItem (final IItem parent, final String name, final String fullVMName)
+    {
+        super (parent);
+        
+        m_name = name;
+        m_fullVMName = fullVMName;
+    }
+    
+    public String getName ()
+    {
+        return m_name;
+    }
+    
+    public String getFullVMName ()
+    {
+        return m_fullVMName;
+    }
+    
+    public int getFirstLine ()
+    {
+        // TODO: state validation
+        
+        if (m_firstLine == 0)
+        {
+            getAggregate (TOTAL_LINE_COUNT); // fault line coverage calculation
+        }
+        
+        return m_firstLine;
+    }
+
+    
+    
+    
+    public IntObjectMap /* line_no:int -> LineCoverageData */ getLineCoverage ()
+    {
+        if (m_lineCoverage == null)
+        {
+            getAggregate (TOTAL_LINE_COUNT); // fault line coverage calculation
+        }   
+        
+        return m_lineCoverage;
+    }
+    
+        
+    public int getAggregate (final int type)
+    {
+        final int [] aggregates = m_aggregates;
+
+        int value = aggregates [type];
+        
+        if (value < 0)
+        {
+            switch (type)
+            {
+                case COVERAGE_CLASS_COUNT:
+                case    TOTAL_CLASS_COUNT:
+                {                    
+                    aggregates [TOTAL_CLASS_COUNT] = getChildCount ();
+                    
+                    value = 0;
+                    for (Iterator children = getChildren (); children.hasNext (); )
+                    {
+                        // SF BUG 972725: this was incorrectly using 'type' instead
+                        // of the COVERAGE_CLASS_COUNT aggregate type, making class
+                        // coverage computation dependent on the order of how item
+                        // nodes were traversed in report generators
+                        value += ((IItem) children.next ()).getAggregate (COVERAGE_CLASS_COUNT);
+                    }
+                    aggregates [COVERAGE_CLASS_COUNT] = value;
+
+                    return aggregates [type];
+                }
+                //break;
+                
+                
+                case TOTAL_SRCFILE_COUNT:
+                {
+                    return aggregates [TOTAL_SRCFILE_COUNT] = 1;
+                }
+                //break;
+                
+                
+                case COVERAGE_LINE_COUNT:
+                case    TOTAL_LINE_COUNT:
+                
+                case COVERAGE_LINE_INSTR:
+                {
+                    // line aggregate types are special when used on srcfile items:
+                    // unlike all others, they do not simply add up when the line
+                    // info is available; instead, lines from all classes belonging
+                    // to the same srcfile parent are set-merged 
+                    
+                    final IntObjectMap /* line -> int[2] */ fldata = new IntObjectMap ();
+                    
+                    for (Iterator classes = getChildren (); classes.hasNext (); )
+                    {
+                        final ClassItem cls = (ClassItem) classes.next ();
+                    
+                        final boolean [][] ccoverage = cls.getCoverage (); // this can be null
+                        final ClassDescriptor clsdesc = cls.getClassDescriptor ();
+                        final MethodDescriptor [] methoddescs = clsdesc.getMethods ();
+                        
+                        for (Iterator methods = cls.getChildren (); methods.hasNext (); )
+                        {
+                            final MethodItem method = (MethodItem) methods.next ();
+                            final int methodID = method.getID ();
+                            
+                            final boolean [] mcoverage = ccoverage == null ? null : ccoverage [methodID];
+                            
+                            final MethodDescriptor methoddesc = methoddescs [methodID];                        
+                            final int [] mbsizes = methoddesc.getBlockSizes ();
+                            final IntObjectMap mlineMap = methoddesc.getLineMap ();
+                            if ($assert.ENABLED) $assert.ASSERT (mlineMap != null);
+                            
+                            final int [] mlines = mlineMap.keys ();
+                            for (int ml = 0, mlLimit = mlines.length; ml < mlLimit; ++ ml)
+                            {
+                                final int mline = mlines [ml];
+                                
+                                int [] data = (int []) fldata.get (mline);
+                                if (data == null)
+                                {
+                                    data = new int [4]; // { totalcount, totalinstr, coveragecount, coverageinstr }
+                                    fldata.put (mline, data);
+                                }
+                                
+                                final int [] lblocks = (int []) mlineMap.get (mline);
+                                
+                                final int bCount = lblocks.length; 
+                                data [0] += bCount;
+                                
+                                for (int bID = 0; bID < bCount; ++ bID)
+                                {
+                                    final int block = lblocks [bID];
+                                    
+                                    final boolean bcovered = mcoverage != null && mcoverage [block];
+                                    final int instr = mbsizes [block];
+                                    
+                                    data [1] += instr;
+                                    if (bcovered)
+                                    {
+                                        ++ data [2];
+                                        data [3] += instr;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    
+                    final int lineCount = fldata.size ();
+                    
+                    aggregates [TOTAL_LINE_COUNT] = lineCount;
+                    
+                    int coverageLineCount = 0;
+                    int coverageLineInstr = 0;
+                    
+                    final IntObjectMap /* line_no:int -> LineCoverageData */ lineCoverage = new IntObjectMap (lineCount);
+                    int firstLine = Integer.MAX_VALUE;
+                    
+                    final int [] clines = fldata.keys ();
+                    
+                    for (int cl = 0; cl < lineCount; ++ cl)
+                    {
+                        final int cline = clines [cl];
+                        final int [] data = (int []) fldata.get (cline);
+                        
+                        final int ltotalCount = data [0];
+                        final int ltotalInstr = data [1];
+                        final int lcoverageCount = data [2];
+                        final int lcoverageInstr = data [3];
+                        
+                        if (lcoverageInstr > 0)
+                        {
+                            coverageLineCount += (PRECISION * lcoverageCount) / ltotalCount;
+                            coverageLineInstr += (PRECISION * lcoverageInstr) / ltotalInstr;
+                        }
+                        
+                        // side effect: populate line coverage data map [used by getLineCoverage()]
+                        
+                        final int lcoverageStatus;
+                        int [][] lcoverageRatio = null;
+                        
+                        if (lcoverageInstr == 0)
+                            lcoverageStatus = LineCoverageData.LINE_COVERAGE_ZERO;
+                        else if (lcoverageInstr == ltotalInstr)
+                            lcoverageStatus = LineCoverageData.LINE_COVERAGE_COMPLETE;
+                        else
+                        {
+                            lcoverageStatus = LineCoverageData.LINE_COVERAGE_PARTIAL;
+                            lcoverageRatio = new int [][] {{ltotalCount, lcoverageCount}, {ltotalInstr, lcoverageInstr}}; // note: ordering depends on IItemAttribute.UNITS_xxx 
+                        }
+                        
+                        lineCoverage.put (cline, new LineCoverageData (lcoverageStatus, lcoverageRatio));
+                        
+                        // side effect: compute m_firstLine
+                        
+                        if (cline < firstLine) firstLine = cline;
+                    }
+                    
+                    m_lineCoverage = lineCoverage; // side effect
+                    m_firstLine = firstLine; // side effect
+                    
+                    aggregates [COVERAGE_LINE_COUNT] = coverageLineCount;
+                    aggregates [COVERAGE_LINE_INSTR] = coverageLineInstr;
+                    
+                    return aggregates [type];
+                }
+                //break;
+
+                            
+                default: return super.getAggregate (type);
+            }
+        }
+        
+        return value;
+    }
+
+    
+    public void accept (final IItemVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public final IItemMetadata getMetadata ()
+    {
+        return METADATA;
+    }
+    
+    public static IItemMetadata getTypeMetadata ()
+    {
+        return METADATA;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+    
+    private final String m_name, m_fullVMName;
+    private IntObjectMap /* line_no:int -> LineCoverageData */ m_lineCoverage;
+    private int m_firstLine;
+    
+    private static final Item.ItemMetadata METADATA; // set in <clinit>
+        
+    static
+    {
+        METADATA = new Item.ItemMetadata (IItemMetadata.TYPE_ID_SRCFILE, "srcfile",
+            1 << IItemAttribute.ATTRIBUTE_NAME_ID |
+            1 << IItemAttribute.ATTRIBUTE_CLASS_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_METHOD_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_BLOCK_COVERAGE_ID |
+            1 << IItemAttribute.ATTRIBUTE_LINE_COVERAGE_ID);
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/ReportGenerator.java b/core/java12/com/vladium/emma/report/html/ReportGenerator.java
new file mode 100644
index 0000000..7dd9195
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/ReportGenerator.java
@@ -0,0 +1,1640 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportGenerator.java,v 1.2.2.1 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.emma.report.html;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.vladium.util.Descriptors;
+import com.vladium.util.Files;
+import com.vladium.util.IProperties;
+import com.vladium.util.IntObjectMap;
+import com.vladium.util.IntVector;
+import com.vladium.util.ObjectIntMap;
+import com.vladium.util.Property;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.report.AbstractReportGenerator;
+import com.vladium.emma.report.AllItem;
+import com.vladium.emma.report.ClassItem;
+import com.vladium.emma.report.IItem;
+import com.vladium.emma.report.IItemAttribute;
+import com.vladium.emma.report.IItemMetadata;
+import com.vladium.emma.report.ItemComparator;
+import com.vladium.emma.report.MethodItem;
+import com.vladium.emma.report.PackageItem;
+import com.vladium.emma.report.SourcePathCache;
+import com.vladium.emma.report.SrcFileItem;
+import com.vladium.emma.report.html.doc.*;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ReportGenerator extends AbstractReportGenerator
+                            implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    // TODO: make sure relative file names are converted to relative URLs in all anchors/hrefs
+
+    public ReportGenerator ()
+    {
+        m_format = (DecimalFormat) NumberFormat.getPercentInstance (); // TODO: locale
+        m_fieldPosition = new FieldPosition (DecimalFormat.INTEGER_FIELD);
+        
+        m_format.setMaximumFractionDigits (0);
+    }
+
+        
+    // IReportGenerator:
+    
+    public final String getType ()
+    {
+        return TYPE;
+    }
+    
+    public void process (final IMetaData mdata, final ICoverageData cdata,
+                         final SourcePathCache cache, final IProperties properties)
+        throws EMMARuntimeException
+    {
+        initialize (mdata, cdata, cache, properties);
+        
+        m_pageTitle = null;
+        m_footerBottom = null;
+        
+        File outDir = m_settings.getOutDir ();
+        if ((outDir == null) /* this should never happen */ || (outDir.equals (new File (Property.getSystemProperty ("user.dir", "")))))
+        {
+            outDir = new File ("coverage");
+            m_settings.setOutDir (outDir);
+        }        
+        
+        long start = 0, end;
+        final boolean trace1 = m_log.atTRACE1 ();
+        
+        if (trace1) start = System.currentTimeMillis ();
+        
+        {
+            m_queue = new LinkedList ();
+            m_reportIDNamespace = new IDGenerator (mdata.size ());
+            
+            for (m_queue.add (m_view.getRoot ()); ! m_queue.isEmpty (); )
+            {
+                final IItem head = (IItem) m_queue.removeFirst ();
+                
+                head.accept (this, null);
+            }
+            
+            m_reportIDNamespace = null;
+        }
+        
+        if (trace1)
+        {
+            end = System.currentTimeMillis ();
+            
+            m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms");
+        }
+    }
+    
+    public void cleanup ()
+    {
+        m_queue = null;
+        m_reportIDNamespace = null;
+        
+        super.cleanup ();
+    }
+
+        
+    // IItemVisitor:
+    
+    public Object visit (final AllItem item, final Object ctx)
+    {
+        HTMLWriter out = null;
+        try
+        {
+            File outFile = m_settings.getOutFile ();
+            if (outFile == null)
+            {
+                outFile = new File ("index".concat (FILE_EXTENSION));
+                m_settings.setOutFile (outFile);
+            }
+            
+            final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile);
+            
+            m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ...");
+            
+            out = openOutFile (fullOutFile, m_settings.getOutEncoding (), true);
+            
+            final int [] columns = m_settings.getColumnOrder ();
+            final StringBuffer buf = new StringBuffer ();
+            
+            final String title;
+            {
+                final StringBuffer _title = new StringBuffer (REPORT_HEADER_TITLE);
+                
+                _title.append (" (generated ");
+                _title.append (new Date (EMMAProperties.getTimeStamp ()));
+                _title.append (')');
+                
+                title = _title.toString ();
+            }
+            
+            final HTMLDocument page = createPage (title);            
+            {
+                final IItem [] path = getParentPath (item);
+                
+                addPageHeader (page, item, path);
+                addPageFooter (page, item, path);
+            }
+                        
+            // [all] coverage summary table:
+            
+            page.addH (1, "OVERALL COVERAGE SUMMARY", null);
+            
+            final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                // header row:
+                final HTMLTable.IRow header = summaryTable.newTitleRow ();
+                // coverage row:
+                final HTMLTable.IRow coverage = summaryTable.newRow ();
+                
+                for (int c = 0; c < columns.length; ++ c)
+                {
+                    final int attrID = columns [c];
+                    final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+                    
+                    final HTMLTable.ICell headercell = header.newCell ();
+                    headercell.setText (attr.getName (), true);
+                    
+                    if (attr != null)
+                    {
+                        boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                        
+                        buf.setLength (0);
+                        attr.format (item, buf);
+                        
+                        final HTMLTable.ICell cell = coverage.newCell (); 
+                        cell.setText (buf.toString (), true);
+                        if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
+                    }
+                }
+            }
+            page.add (summaryTable);
+            
+            // [all] stats summary table ([all] only):
+
+            page.addH (2, "OVERALL STATS SUMMARY", null);
+            
+            final HTMLTable statsTable = new HTMLTable (null, null, null, "0");
+            statsTable.setClass (CSS_INVISIBLE_TABLE);
+            {
+                HTMLTable.IRow row = statsTable.newRow ();
+                row.newCell ().setText ("total packages:", true);
+                row.newCell ().setText ("" + item.getChildCount (), false);
+                
+                if (m_srcView && m_hasSrcFileInfo)
+                {
+                    row = statsTable.newRow ();
+                    row.newCell ().setText ("total executable files:", true);
+                    row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT), false);
+                }
+                
+                row = statsTable.newRow ();
+                row.newCell ().setText ("total classes:", true);
+                row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_CLASS_COUNT), true);
+                row = statsTable.newRow ();
+                row.newCell ().setText ("total methods:", true);
+                row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_METHOD_COUNT), true);
+                
+                if (m_srcView && m_hasSrcFileInfo && m_hasLineNumberInfo)
+                {
+                    row = statsTable.newRow ();
+                    row.newCell ().setText ("total executable lines:", true);
+                    row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_LINE_COUNT), true);
+                }
+            }
+            /*
+            {
+                final HTMLTable.IRow first = statsTable.newRow (); // stats always available
+                
+                first.newCell ().setText ("total packages: " + item.getChildCount (), true);
+                first.newCell ().setText ("total classes: " + item.getAggregate (IItem.TOTAL_CLASS_COUNT), true);
+                first.newCell ().setText ("total methods: " + item.getAggregate (IItem.TOTAL_METHOD_COUNT), true);
+                
+                if (m_srcView && m_hasSrcFileInfo)
+                {
+                    final HTMLTable.IRow second = statsTable.newRow ();
+                    
+                    final HTMLTable.ICell cell1 = second.newCell ();
+                    cell1.setText ("total source files: " + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT), true);
+                
+                    if (m_hasLineNumberInfo)
+                    {
+                        final HTMLTable.ICell cell2 = second.newCell ();
+                        
+                        cell2.setText ("total executable source lines: " + item.getAggregate (IItem.TOTAL_LINE_COUNT), true);
+                        cell2.getAttributes ().set (Attribute.COLSPAN, "2");
+                    }
+                    else
+                    {
+                        cell1.getAttributes ().set (Attribute.COLSPAN, "3");
+                    }
+                }
+            }
+            */
+            page.add (statsTable);
+            
+            final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
+            
+            // render package summary tables on the same page:
+            
+            page.addH (2, "COVERAGE BREAKDOWN BY PACKAGE", null);
+            
+            final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                int [] headerColumns = null;
+                
+                boolean odd = true;
+                final ItemComparator order = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()];
+                for (Iterator packages = item.getChildren (order); packages.hasNext (); odd = ! odd)
+                {
+                    final IItem pkg = (IItem) packages.next ();
+                    
+                    if (headerColumns == null)
+                    {
+                        // header row:
+                        headerColumns = addHeaderRow (pkg, childSummaryTable, columns);                        
+                    }
+                    
+                    // coverage row:
+                    String childHREF = null;
+                    if (deeper)
+                    {
+                        childHREF = getItemHREF (item, pkg);
+                    }
+                    addItemRow (pkg, odd, childSummaryTable, headerColumns, childHREF, false);
+                    
+                    if (deeper) m_queue.addLast (pkg);
+                }
+            }
+            page.add (childSummaryTable);
+            
+            
+            page.emit (out);            
+            out.flush ();
+        }
+        finally
+        {
+            if (out != null) out.close ();
+            out = null;
+        }
+        
+        return ctx;
+    }
+    
+    public Object visit (final PackageItem item, final Object ctx)
+    {
+        HTMLWriter out = null;
+        try
+        {
+            if (m_verbose) m_log.verbose ("  report: processing package [" + item.getName () + "] ...");
+            
+            final File outFile = getItemFile (NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID (getItemKey (item)));
+            
+            out = openOutFile (Files.newFile (m_settings.getOutDir (), outFile), m_settings.getOutEncoding (), true);
+            
+            final int [] columns = m_settings.getColumnOrder ();            
+            final StringBuffer buf = new StringBuffer ();
+            
+            // TODO: set title [from a prop?]
+            final HTMLDocument page = createPage (REPORT_HEADER_TITLE);
+            {
+                final IItem [] path = getParentPath (item);
+                
+                addPageHeader (page, item, path);
+                addPageFooter (page, item, path);
+            }
+                        
+            // summary table:
+            
+            {
+                final IElement itemname = IElement.Factory.create (Tag.SPAN);
+                itemname.setText (item.getName (), true);
+                itemname.setClass (CSS_ITEM_NAME);
+                
+                final IElementList title = new ElementList ();
+                title.add (new Text ("COVERAGE SUMMARY FOR PACKAGE [", true));
+                title.add (itemname);
+                title.add (new Text ("]", true));
+                
+                page.addH (1, title, null);
+            }
+            
+            final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                // header row:
+                final HTMLTable.IRow header = summaryTable.newTitleRow ();
+                // coverage row:
+                final HTMLTable.IRow coverage = summaryTable.newRow ();
+                
+                for (int c = 0; c < columns.length; ++ c)
+                {
+                    final int attrID = columns [c];
+                    final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+                    
+                    final HTMLTable.ICell headercell = header.newCell ();
+                    headercell.setText (attr.getName (), true);
+                    
+                    if (attr != null)
+                    {
+                        boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                        
+                        buf.setLength (0);
+                        attr.format (item, buf);
+                        
+                        final HTMLTable.ICell cell = coverage.newCell (); 
+                        cell.setText (buf.toString (), true);
+                        if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
+                    }
+                }
+            }
+            page.add (summaryTable);
+            
+            final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
+            
+            // render child summary tables on the same page:
+            
+            final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE" : "COVERAGE BREAKDOWN BY CLASS";
+            page.addH (2, summaryTitle, null);
+            
+            final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                int [] headerColumns = null;
+                
+                boolean odd = true;
+                final ItemComparator order = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()];                
+                for (Iterator srcORclsFiles = item.getChildren (order); srcORclsFiles.hasNext (); odd = ! odd)
+                {
+                    final IItem srcORcls = (IItem) srcORclsFiles.next ();
+                    
+                    if (headerColumns == null)
+                    {
+                        // header row:
+                        headerColumns = addHeaderRow (srcORcls, childSummaryTable, columns);                        
+                    }
+                    
+                    // coverage row:
+                    String childHREF = null;
+                    if (deeper)
+                    {
+                        childHREF = getItemHREF (item, srcORcls);
+                    }
+                    addItemRow (srcORcls, odd, childSummaryTable, headerColumns, childHREF, false);
+                                        
+                    if (deeper) m_queue.addLast (srcORcls);
+                }
+            }
+            page.add (childSummaryTable);
+            
+            
+            page.emit (out);            
+            out.flush ();
+        }
+        finally
+        {
+            if (out != null) out.close ();
+            out = null;
+        }
+        
+        return ctx;
+    }
+
+    public Object visit (final SrcFileItem item, final Object ctx)
+    {
+        // this visit only takes place in src views
+        
+        HTMLWriter out = null;
+        try
+        {
+            final File outFile = getItemFile (NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID (getItemKey (item)));
+            
+            out = openOutFile (Files.newFile (m_settings.getOutDir (), outFile), m_settings.getOutEncoding (), true);
+            
+            final int [] columns = m_settings.getColumnOrder ();            
+            final StringBuffer buf = new StringBuffer ();
+            
+            // TODO: set title [from a prop?]
+            final HTMLDocument page = createPage (REPORT_HEADER_TITLE);
+            {
+                final IItem [] path = getParentPath (item);
+                
+                addPageHeader (page, item, path);
+                addPageFooter (page, item, path);
+            }
+            
+            // summary table:
+            
+            {
+                final IElement itemname = IElement.Factory.create (Tag.SPAN);
+                itemname.setText (item.getName (), true);
+                itemname.setClass (CSS_ITEM_NAME);
+                
+                final IElementList title = new ElementList ();
+                title.add (new Text ("COVERAGE SUMMARY FOR SOURCE FILE [", true));
+                title.add (itemname);
+                title.add (new Text ("]", true));
+                
+                page.addH (1, title, null);
+            }
+            
+            final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                // header row:
+                final HTMLTable.IRow header = summaryTable.newTitleRow ();
+                // coverage row:
+                final HTMLTable.IRow coverage = summaryTable.newRow ();
+                
+                for (int c = 0; c < columns.length; ++ c)
+                {
+                    final int attrID = columns [c];
+                    final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+                    
+                    final HTMLTable.ICell headercell = header.newCell ();
+                    headercell.setText (attr.getName (), true);
+                    
+                    if (attr != null)
+                    {
+                        boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                        
+                        buf.setLength (0);
+                        attr.format (item, buf);
+                        
+                        final HTMLTable.ICell cell = coverage.newCell (); 
+                        cell.setText (buf.toString (), true);
+                        if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
+                    }
+                }
+            }
+            page.add (summaryTable);    
+            
+            final boolean deeper = (m_settings.getDepth () > ClassItem.getTypeMetadata ().getTypeID ());
+            final boolean embedSrcFile = deeper && srcFileAvailable (item, m_cache);
+            final boolean createAnchors = embedSrcFile && m_hasLineNumberInfo;
+            
+            final IDGenerator pageIDNamespace = createAnchors ? new IDGenerator () : null;
+            
+            // child summary table is special for srcfile items:
+            
+            page.addH (2, "COVERAGE BREAKDOWN BY CLASS AND METHOD", null);
+
+            final IntObjectMap lineAnchorIDMap = embedSrcFile ? new IntObjectMap () : null;
+            final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
+            
+            childSummaryTable.setClass (CSS_CLS_NOLEFT);
+            
+            {
+                int [] headerColumns = null;
+
+                final ItemComparator order = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()];
+                int clsIndex = 0;                
+                for (Iterator classes = item.getChildren (order); classes.hasNext (); ++ clsIndex)
+                {
+                    final ClassItem cls = (ClassItem) classes.next ();
+
+                    if (headerColumns == null)
+                    {
+                        // header row:
+                        headerColumns = addHeaderRow (cls, childSummaryTable, columns);                        
+                    }
+                    
+                    String HREFname = null;
+                    
+                    // special class subheader:
+                    if (createAnchors)
+                    {
+                        if ($assert.ENABLED)
+                        {
+                            $assert.ASSERT (lineAnchorIDMap != null);
+                            $assert.ASSERT (pageIDNamespace != null);
+                        }
+                        
+                        final String childKey = getItemKey (cls);
+                        
+                        HREFname = addLineAnchorID (cls.getFirstLine (), pageIDNamespace.getID (childKey), lineAnchorIDMap);
+                    }
+
+                    addClassRow (cls, clsIndex, childSummaryTable, headerColumns, HREFname, createAnchors);
+                    
+//                    // row to separate this class's methods:
+//                    final HTMLTable.IRow subheader = childSummaryTable.newTitleRow ();
+//                    final HTMLTable.ICell cell = subheader.newCell ();
+//                    // TODO: cell.setColspan (???)
+//                    cell.setText ("class " + child.getName () + " methods:", true);
+                    
+                    boolean odd = false;
+                    final ItemComparator order2 = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];                
+                    for (Iterator methods = cls.getChildren (order2); methods.hasNext (); odd = ! odd)
+                    {
+                        final MethodItem method = (MethodItem) methods.next ();
+                        
+                        HREFname = null;
+                        
+                        if (createAnchors)
+                        {
+                            if ($assert.ENABLED)
+                            {
+                                $assert.ASSERT (lineAnchorIDMap != null);
+                                $assert.ASSERT (pageIDNamespace != null);
+                            }
+                            
+                            final String child2Key = getItemKey (method);
+                            
+                            HREFname = addLineAnchorID (method.getFirstLine (), pageIDNamespace.getID (child2Key), lineAnchorIDMap);
+                        }
+
+                        addClassItemRow (method, odd, childSummaryTable, headerColumns, HREFname, createAnchors);
+                    }
+                }
+            }
+            page.add (childSummaryTable);
+            
+            
+            // embed source file:
+             
+            if (deeper)
+            {
+                //page.addHR (1);
+                page.addEmptyP ();
+                {
+                    embedSrcFile (item, page, lineAnchorIDMap, m_cache);
+                }
+                //page.addHR (1);
+            }
+                
+            
+            page.emit (out);            
+            out.flush ();
+        }
+        finally
+        {
+            if (out != null) out.close ();
+            out = null;
+        }
+
+        return ctx;
+    }
+
+    public Object visit (final ClassItem item, final Object ctx)
+    {
+        // this visit only takes place in class views
+        
+        HTMLWriter out = null;
+        try
+        {
+            final File outFile = getItemFile (NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID (getItemKey (item)));
+            
+            // TODO: deal with overwrites
+            out = openOutFile (Files.newFile (m_settings.getOutDir (), outFile), m_settings.getOutEncoding (), true);
+            
+            final int [] columns = m_settings.getColumnOrder ();            
+            final StringBuffer buf = new StringBuffer ();
+            
+            // TODO: set title [from a prop?]
+            final HTMLDocument page = createPage (REPORT_HEADER_TITLE);
+            {
+                final IItem [] path = getParentPath (item);
+                
+                addPageHeader (page, item, path);
+                addPageFooter (page, item, path);
+            }
+            
+            
+            // summary table:
+            
+            {
+                final IElement itemname = IElement.Factory.create (Tag.SPAN);
+                itemname.setText (item.getName (), true);
+                itemname.setClass (CSS_ITEM_NAME);
+                
+                final IElementList title = new ElementList ();
+                title.add (new Text ("COVERAGE SUMMARY FOR CLASS [", true));
+                title.add (itemname);
+                title.add (new Text ("]", true));
+                
+                page.addH (1, title, null);
+            }
+            
+            final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                // header row:
+                final HTMLTable.IRow header = summaryTable.newTitleRow ();
+                // coverage row:
+                final HTMLTable.IRow coverage = summaryTable.newRow ();
+                
+                for (int c = 0; c < columns.length; ++ c)
+                {
+                    final int attrID = columns [c];
+                    final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+                    
+                    final HTMLTable.ICell headercell = header.newCell ();
+                    headercell.setText (attr.getName (), true);
+                    
+                    if (attr != null)
+                    {
+                        boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                        
+                        buf.setLength (0);
+                        attr.format (item, buf);
+                        
+                        final HTMLTable.ICell cell = coverage.newCell (); 
+                        cell.setText (buf.toString (), true);
+                        if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
+                    }
+                }
+            }
+            page.add (summaryTable);
+            
+            
+            // child summary table:
+            
+            page.addH (2, "COVERAGE BREAKDOWN BY METHOD", null);
+
+            final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
+            {
+                int [] headerColumns = null;
+                
+                boolean odd = true;
+                final ItemComparator order = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];                
+                for (Iterator methods = item.getChildren (order); methods.hasNext (); odd = ! odd)
+                {
+                    final MethodItem method = (MethodItem) methods.next ();
+
+                    if (headerColumns == null)
+                    {
+                        // header row:
+                        headerColumns = addHeaderRow (method, childSummaryTable, columns);                        
+                    }
+                    
+                    addItemRow (method, odd, childSummaryTable, headerColumns, null, false);
+                }
+            }
+            page.add (childSummaryTable);
+            
+            
+            page.emit (out);            
+            out.flush ();
+        }
+        finally
+        {
+            if (out != null) out.close ();
+            out = null;
+        }
+
+        return ctx;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class IDGenerator
+    {
+        IDGenerator ()
+        {
+            m_namespace = new ObjectIntMap (101);
+            m_out = new int [1];
+        }
+        
+        IDGenerator (final int initialCapacity)
+        {
+            m_namespace = new ObjectIntMap (initialCapacity);
+            m_out = new int [1];
+        }
+        
+        String getID (final String key)
+        {
+            final int [] out = m_out;
+            final int ID;
+            
+            if (m_namespace.get (key, out))
+                ID = out [0];
+            else
+            {
+                ID = m_namespace.size ();
+                m_namespace.put (key, ID);
+            }
+            
+            return Integer.toHexString (ID);
+        }
+        
+        private final ObjectIntMap /* key:String->ID */ m_namespace;
+        private final int [] m_out;
+        
+    } // end of nested class
+    
+    
+    private HTMLDocument createPage (final String title)
+    {
+        final HTMLDocument page = new HTMLDocument (title, m_settings.getOutEncoding ());
+        page.addStyle (CSS); // TODO: split by visit type
+        
+        return page;
+    }
+    
+    private IElement addPageHeader (final HTMLDocument page, final IItem item, final IItem [] path)
+    {
+        // TODO: merge header and footer in the same method
+        
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (page != null);
+        }
+        
+        final HTMLTable header = new HTMLTable ("100%", null, null, "0");
+        header.setClass (CSS_HEADER_FOOTER);
+
+        // header row:        
+        addPageHeaderTitleRow (header);
+        
+        // nav row:
+        {
+            final HTMLTable.IRow navRow = header.newRow ();
+            
+            final HTMLTable.ICell cell = navRow.newCell ();
+            cell.setClass (CSS_NAV);
+            
+            final int lLimit = path.length > 1 ? path.length - 1 : path.length;
+            for (int l = 0; l < lLimit; ++ l)
+            {
+                cell.add (LEFT_BRACKET);
+                
+                final String name = path [l].getName ();
+                final String HREF = getItemHREF (item, path [l]);
+                cell.add (new HyperRef (HREF, name, true));
+                
+                cell.add (RIGHT_BRACKET);
+            }
+        }
+        
+        page.setHeader (header);
+        
+        return header;
+    }
+    
+    private IElement addPageFooter (final HTMLDocument page, final IItem item, final IItem [] path)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (page != null);
+        }
+
+        final HTMLTable footerTable = new HTMLTable ("100%", null, null, "0");
+        footerTable.setClass (CSS_HEADER_FOOTER);
+
+        // nav row:
+        {
+            final HTMLTable.IRow navRow = footerTable.newRow ();
+            
+            final HTMLTable.ICell cell = navRow.newCell ();
+            cell.setClass (CSS_NAV);
+            
+            final int lLimit = path.length > 1 ? path.length - 1 : path.length;
+            for (int l = 0; l < lLimit; ++ l)
+            {
+                cell.add (LEFT_BRACKET);
+                
+                final String name = path [l].getName ();
+                final String HREF = getItemHREF (item, path [l]);
+                cell.add (new HyperRef (HREF, name, true));
+                
+                cell.add (RIGHT_BRACKET);
+            }
+        }
+        
+        // title row:
+        {
+            final HTMLTable.IRow titleRow = footerTable.newRow ();
+            
+            final HTMLTable.ICell cell = titleRow.newCell ();
+            cell.setClass (CSS_TITLE);
+            
+            cell.add (getFooterBottom ());
+        }
+        
+        final ElementList footer = new ElementList ();
+        
+        footer.add (IElement.Factory.create (Tag.P)); // spacer
+        footer.add (footerTable);
+        
+        page.setFooter (footer);
+        
+        return footerTable;
+    }
+    
+    private int [] addHeaderRow (final IItem item, final HTMLTable table, final int [] columns)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (table != null, "null input: table");
+            $assert.ASSERT (columns != null, "null input: columns");
+        }
+        
+        // header row:
+        final HTMLTable.IRow header = table.newTitleRow ();
+        
+        // determine the set of columns actually present in the header [may be narrower than 'columns']:
+        final IntVector headerColumns = new IntVector (columns.length);
+        
+        for (int c = 0; c < columns.length; ++ c)
+        {
+            final int attrID = columns [c];
+            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+            
+            if (attr != null)
+            {
+                final HTMLTable.ICell cell = header.newCell ();
+            
+                cell.setText (attr.getName (), true);//.getAttributes ().set (Attribute.WIDTH, "20%");
+                cell.setClass (headerCellStyle (c));
+                headerColumns.add (attrID);
+            }
+            
+            // note: by design this does not create columns for nonexistent attribute types
+        }
+        
+        return headerColumns.values ();
+    }
+    
+    /*
+     * No header row, just data rows.
+     */
+    private void addItemRow (final IItem item, final boolean odd, final HTMLTable table, final int [] columns, final String nameHREF, final boolean anchor)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (table != null, "null input: table");
+            $assert.ASSERT (columns != null, "null input: columns");
+        }
+        
+        final HTMLTable.IRow row = table.newRow ();
+        if (odd) row.setClass (CSS_ODDROW);
+        
+        final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer
+        
+        for (int c = 0; c < columns.length; ++ c)
+        {
+            final int attrID = columns [c];
+            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+            
+            if (attr != null)
+            {
+                final HTMLTable.ICell cell = row.newCell ();
+                
+                if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID))
+                {
+                    buf.setLength (0);
+                    attr.format (item, buf);
+                    
+                    trimForDisplay (buf);
+                    
+                    final String fullHREFName = anchor ? "#".concat (nameHREF) : nameHREF; 
+                    
+                    cell.add (new HyperRef (fullHREFName, buf.toString (), true));
+                }
+                else
+                {
+                    final boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                    
+                    buf.setLength (0);
+                    attr.format (item, buf);
+                    
+                    trimForDisplay (buf);
+                     
+                    cell.setText (buf.toString (), true);
+                    if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
+                }
+            }
+            else
+            {
+                // note: by design this puts empty cells for nonexistent attribute types
+                
+                final HTMLTable.ICell cell = row.newCell (); 
+                cell.setText (" ", true);
+            }
+        }
+    }
+    
+    private void addClassRow (final ClassItem item, final int clsIndex, final HTMLTable table, final int [] columns,
+                              final String itemHREF, final boolean isAnchor)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (table != null, "null input: table");
+            $assert.ASSERT (columns != null, "null input: columns");
+        }
+        
+        final HTMLTable.IRow blank = table.newRow ();
+        
+        final HTMLTable.IRow row = table.newRow ();
+        row.setClass (CSS_CLASS_ITEM_SPECIAL);
+        
+        final StringBuffer buf = new StringBuffer (11);
+        
+        for (int c = 0; c < columns.length; ++ c)
+        {
+            final int attrID = columns [c];
+            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+            
+            if (attr != null)
+            {
+                buf.setLength (0);
+                attr.format (item, buf);
+                
+                final HTMLTable.ICell blankcell = blank.newCell ();
+                blankcell.setClass (clsIndex == 0 ? CSS_BLANK : CSS_BOTTOM);
+                blankcell.setText (" ", true);
+                
+                final HTMLTable.ICell cell = row.newCell ();
+                
+                boolean fail = false;
+                if (attrID == IItemAttribute.ATTRIBUTE_NAME_ID)
+                {
+                    if (itemHREF != null)
+                    {
+                        final String fullItemHREF = isAnchor ? "#".concat (itemHREF) : itemHREF;
+                        
+                        cell.add (new Text ("class ", true));
+                        cell.add (new HyperRef (fullItemHREF, buf.toString (), true));
+                    }
+                    else
+                    {
+                        cell.setText ("class " + buf.toString (), true);
+                    }
+                }
+                else
+                {
+                    fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                    
+                    cell.setText (buf.toString (), true);
+                }
+                
+                cell.setClass (dataCellStyle (c, fail));
+            }
+            else
+            {
+                final HTMLTable.ICell cell = row.newCell (); 
+                cell.setText (" ", true);
+                cell.setClass (dataCellStyle (c, false));
+            }
+        }
+    }
+    
+    
+    private void addClassItemRow (final IItem item, final boolean odd, final HTMLTable table, final int [] columns, final String nameHREF, final boolean anchor)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (table != null, "null input: table");
+            $assert.ASSERT (columns != null, "null input: columns");
+        }
+        
+        final HTMLTable.IRow row = table.newRow ();
+        if (odd) row.setClass (CSS_ODDROW);
+        
+        final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer
+        
+        for (int c = 0; c < columns.length; ++ c)
+        {
+            final int attrID = columns [c];
+            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+            
+            if (attr != null)
+            {
+                final HTMLTable.ICell cell = row.newCell ();
+                
+                boolean fail = false;
+                if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID))
+                {
+                    buf.setLength (0);
+                    attr.format (item, buf);
+                    
+                    trimForDisplay (buf);
+                    
+                    final String fullHREFName = anchor ? "#".concat (nameHREF) : nameHREF; 
+                    
+                    cell.add (new HyperRef (fullHREFName, buf.toString (), true));
+                }
+                else
+                {
+                    fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                    
+                    buf.setLength (0);
+                    attr.format (item, buf);
+                    
+                    trimForDisplay (buf);
+                     
+                    cell.setText (buf.toString (), true);
+                }
+                
+                cell.setClass (dataCellStyle (c, fail));
+            }
+            else
+            {
+                // note: by design this puts empty cells for nonexistent attribute types
+                
+                final HTMLTable.ICell cell = row.newCell (); 
+                cell.setText (" ", true);
+                cell.setClass (dataCellStyle (c, false));
+            }
+        }
+    }
+    
+    
+    private boolean srcFileAvailable (final SrcFileItem item, final SourcePathCache cache)
+    {
+        if (cache == null) return false;
+        
+        if ($assert.ENABLED) $assert.ASSERT (item != null, "null input: item");
+        
+        final String fileName = item.getName ();
+        if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
+        
+        // TODO: should I keep VM names in package items?
+        final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
+        
+        return (cache.find (packageVMName, fileName) != null);
+    }
+    
+//    private boolean srcFileAvailable (final ClassItem item, final SourcePathCache cache)
+//    {
+//        if (cache == null) return false;
+//        
+//        if ($assert.ENABLED) $assert.ASSERT (item != null, "null input: item");
+//        
+//        final String fileName = item.getSrcFileName ();
+//        if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
+//        
+//        // TODO: should I keep VM names in package items?
+//        final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
+//        
+//        return (cache.find (packageVMName, fileName) != null);
+//    }
+    
+    private void embedSrcFile (final SrcFileItem item, final HTMLDocument page,
+                               final IntObjectMap /* line num:int->anchor name:String */anchorMap,
+                               final SourcePathCache cache)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (page != null, "null input: page");
+        }
+        
+        final String fileName = item.getName ();
+        if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
+        
+        // TODO: should I keep VM names in package items?
+        final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
+        
+        boolean success = false;
+
+        final HTMLTable srcTable = new HTMLTable ("100%", null, null, "0");
+        
+        if (cache != null) // TODO: do this check earlier, in outer scope
+        {
+            srcTable.setClass (CSS_SOURCE);
+            final File srcFile = cache.find (packageVMName, fileName);
+            
+            if (srcFile != null)
+            {
+                BufferedReader in = null;
+                try
+                {
+                    in = new BufferedReader (new FileReader (srcFile), IO_BUF_SIZE);
+                    
+                    final boolean markupCoverage = m_hasLineNumberInfo;
+                    
+                    final int unitsType = m_settings.getUnitsType ();
+                    IntObjectMap /* line num:int -> SrcFileItem.LineCoverageData */ lineCoverageMap = null;
+                    StringBuffer tooltipBuffer = null;
+                    
+                    
+                    if (markupCoverage)
+                    {
+                        lineCoverageMap = item.getLineCoverage ();
+                        $assert.ASSERT (lineCoverageMap != null, "null: lineCoverageMap");
+                        
+                        tooltipBuffer = new StringBuffer (64);
+                    }
+                    
+                    int l = 1;
+                    for (String line; (line = in.readLine ()) != null; ++ l)
+                    {
+                        final HTMLTable.IRow srcline = srcTable.newRow ();
+                        final HTMLTable.ICell lineNumCell = srcline.newCell ();
+                        lineNumCell.setClass (CSS_LINENUM);
+                        
+                        if (anchorMap != null)
+                        {
+                            final int adjustedl = l < SRC_LINE_OFFSET ? l : l + SRC_LINE_OFFSET;
+                            
+                            final String anchor = (String) anchorMap.get (adjustedl);
+                            if (anchor != null)
+                            {
+                                final IElement a = IElement.Factory.create (Tag.A);
+                                //a.getAttributes ().set (Attribute.ID, anchor); ID anchoring does not work in NS 4.0
+                                a.getAttributes ().set (Attribute.NAME, anchor);
+                                
+                                a.setText (Integer.toString (l), true);
+                                
+                                lineNumCell.add (a);
+                            }
+                            else
+                            {
+                                lineNumCell.setText (Integer.toString (l), true);
+                            }
+                        }
+                        else
+                        {   
+                            lineNumCell.setText (Integer.toString (l), true);
+                        }
+                        
+                        final HTMLTable.ICell lineTxtCell = srcline.newCell ();
+                        lineTxtCell.setText (line.length () > 0 ? line : " ", true);
+                        
+                        if (markupCoverage)
+                        {
+                            final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap.get (l);
+                            
+                            if (lCoverageData != null)
+                            {
+                                switch (lCoverageData.m_coverageStatus)
+                                {
+                                    case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO:
+                                        srcline.setClass (CSS_COVERAGE_ZERO);
+                                    break;
+                                    
+                                    case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL:
+                                    {
+                                        srcline.setClass (CSS_COVERAGE_PARTIAL);
+                                        
+                                        if (USE_LINE_COVERAGE_TOOLTIPS)
+                                        {
+                                            tooltipBuffer.setLength (0);
+                                            
+                                            final int [] coverageRatio = lCoverageData.m_coverageRatio [unitsType];
+                                            
+                                            final int d = coverageRatio [0];
+                                            final int n = coverageRatio [1];
+
+                                            m_format.format ((double) n / d, tooltipBuffer, m_fieldPosition);
+                                            
+                                            tooltipBuffer.append (" line coverage (");
+                                            tooltipBuffer.append (n);
+                                            tooltipBuffer.append (" out of ");
+                                            tooltipBuffer.append (d);
+
+                                            switch (unitsType)
+                                            {
+                                                case IItemAttribute.UNITS_COUNT:
+                                                    tooltipBuffer.append (" basic blocks)");
+                                                break;
+                                                
+                                                case IItemAttribute.UNITS_INSTR:
+                                                    tooltipBuffer.append (" instructions)");
+                                                break;
+                                            }                                            
+                                            
+                                            // [Opera does not display TITLE tooltios on <TR> elements]
+                                            
+                                            lineNumCell.getAttributes ().set (Attribute.TITLE, tooltipBuffer.toString ());
+                                            lineTxtCell.getAttributes ().set (Attribute.TITLE, tooltipBuffer.toString ());
+                                        }
+                                    }
+                                    break;
+                                        
+                                    case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE:
+                                        srcline.setClass (CSS_COVERAGE_COMPLETE);
+                                    break;
+                                    
+                                    default: $assert.ASSERT (false, "invalid line coverage status: " + lCoverageData.m_coverageStatus);
+                                    
+                                } // end of switch
+                            }
+                        }
+                    }
+                    
+                    success = true;
+                }
+                catch (Throwable t)
+                {
+                    t.printStackTrace (System.out); // TODO: logging
+                    success = false;
+                }
+                finally
+                {
+                    if (in != null) try { in.close (); } catch (Throwable ignore) {}
+                    in = null;
+                }
+            }
+        }
+        
+        if (! success)
+        {
+            srcTable.setClass (CSS_INVISIBLE_TABLE);
+            
+            final HTMLTable.IRow row = srcTable.newTitleRow ();
+            row.newCell ().setText ("[source file '" + Descriptors.combineVMName (packageVMName, fileName) + "' not found in sourcepath]", false);
+        }
+        
+        page.add (srcTable);
+    }
+    
+    
+    private static String addLineAnchorID (final int line, final String anchorID,
+                                           final IntObjectMap /* line num:int->anchorID:String */lineAnchorIDMap)
+    {
+        if (line > 0)
+        {
+            final String _anchorID = (String) lineAnchorIDMap.get (line);
+            if (_anchorID != null)
+                return _anchorID;
+            else
+            {
+                lineAnchorIDMap.put (line, anchorID);
+
+                return anchorID;
+            }
+        }
+        
+        return null;
+    }
+    
+    /*
+     * Always includes AllItem
+     */
+    private IItem [] getParentPath (IItem item)
+    {
+        final LinkedList /* IItem */ _result = new LinkedList ();
+        
+        for ( ; item != null; item = item.getParent ())
+        {
+            _result.add (item);
+        }
+        
+        final IItem [] result = new IItem [_result.size ()];
+        int j = result.length - 1;
+        for (Iterator i = _result.iterator (); i.hasNext (); -- j)
+        {
+            result [j] = (IItem) i.next ();
+        }
+        
+        return result;
+    }
+
+    /*
+     * 
+     */
+    private String getItemHREF (final IItem base, final IItem item)
+    {
+        final String itemHREF;
+        if (item instanceof AllItem)
+            itemHREF = m_settings.getOutFile ().getName (); // note that this is always a simple filename [no parent path]
+        else
+            itemHREF = m_reportIDNamespace.getID (getItemKey (item)).concat (FILE_EXTENSION);
+             
+        final String fullHREF;
+        
+        if (base == null)
+            fullHREF = itemHREF;
+        else
+        {
+            final int nesting = NESTING [base.getMetadata ().getTypeID ()] [item.getMetadata ().getTypeID ()];
+            if (nesting == 1)
+                fullHREF = NESTED_ITEMS_PARENT_DIRNAME.concat ("/").concat (itemHREF);
+            else if (nesting == -1)
+                fullHREF = "../".concat (itemHREF);
+            else
+                fullHREF = itemHREF;
+        }
+        
+        return fullHREF;
+    }
+    
+    
+    private IContent getPageTitle ()
+    { 
+        IContent title = m_pageTitle;
+        if (title == null)
+        {
+            final IElementList _title = new ElementList ();
+            
+            _title.add (new HyperRef (IAppConstants.APP_HOME_SITE_LINK, IAppConstants.APP_NAME, true));
+            
+            final StringBuffer s = new StringBuffer (" Coverage Report (generated ");
+            s.append (new Date (EMMAProperties.getTimeStamp ()));
+            s.append (')');
+            
+            _title.add (new Text (s.toString (), true));
+            
+            m_pageTitle = title = _title;
+        }
+        
+        return title;
+    }
+    
+    private IContent getFooterBottom ()
+    { 
+        IContent bottom = m_footerBottom;
+        if (bottom == null)
+        {
+            final IElementList _bottom = new ElementList ();
+            
+            _bottom.add (new HyperRef (IAppConstants.APP_BUG_REPORT_LINK, IAppConstants.APP_NAME + " " + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG, true));
+            _bottom.add (new Text (" " + IAppConstants.APP_COPYRIGHT, true));
+            
+            m_footerBottom = bottom = _bottom;
+        }
+        
+        return bottom;
+    }
+    
+    private void addPageHeaderTitleRow (final HTMLTable header)
+    {
+        final HTMLTable.IRow titleRow = header.newTitleRow ();
+        
+        final HTMLTable.ICell cell = titleRow.newCell ();
+        cell.setClass (CSS_TITLE);
+        cell.add (getPageTitle ());
+    }
+    
+    private static void trimForDisplay (final StringBuffer buf)
+    {
+        if (buf.length () > MAX_DISPLAY_NAME_LENGTH)
+        {
+            buf.setLength (MAX_DISPLAY_NAME_LENGTH - 3);
+            buf.append ("...");
+        }
+    }
+    
+    /*
+     * Assumes relative pathnames.
+     */
+    private static File getItemFile (final File parentDir, final String itemKey)
+    {
+        if (parentDir == null)
+            return new File (itemKey.concat (FILE_EXTENSION));
+        else
+            return new File (parentDir, itemKey.concat (FILE_EXTENSION));
+    }
+
+    private static String getItemKey (IItem item)
+    {
+        final StringBuffer result = new StringBuffer ();
+        
+        for ( ; item != null; item = item.getParent ())
+        {
+            result.append (item.getName ());
+            result.append (':');
+        }
+        
+        return result.toString ();
+    }
+
+    private static HTMLWriter openOutFile (final File file, final String encoding, final boolean mkdirs)
+    {
+        BufferedWriter out = null;
+        try
+        {
+            if (mkdirs)
+            {
+                final File parent = file.getParentFile ();
+                if (parent != null) parent.mkdirs ();
+            }
+            
+            out = new BufferedWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE);
+        }
+        catch (UnsupportedEncodingException uee)
+        {
+            // TODO: error code
+            throw new EMMARuntimeException (uee);
+        }
+        // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
+        // was narrowed to FileNotFoundException:
+        catch (IOException fnfe) // FileNotFoundException  
+        {
+            // TODO: error code
+            throw new EMMARuntimeException (fnfe);
+        }
+        
+        return new HTMLWriter (out);
+    }
+    
+    private static String dataCellStyle (final int column, final boolean highlight)
+    {
+        if (column == 0)
+            return highlight ? CSS_DATA_HIGHLIGHT_FIRST : CSS_DATA_FIRST;
+        else
+            return highlight ? CSS_DATA_HIGHLIGHT : CSS_DATA;
+    }
+    
+    private static String headerCellStyle (final int column)
+    {
+        return (column == 0) ? CSS_HEADER_FIRST : CSS_HEADER;
+    }
+
+    
+    private final DecimalFormat m_format;
+    private final FieldPosition m_fieldPosition;
+        
+    private LinkedList /* IITem */ m_queue;
+    private IDGenerator m_reportIDNamespace;
+    
+    private IContent m_pageTitle, m_footerBottom;
+    
+    private static final boolean USE_LINE_COVERAGE_TOOLTIPS = true;
+    
+    private static final String TYPE = "html";
+    private static final String REPORT_HEADER_TITLE = IAppConstants.APP_NAME + " Coverage Report";
+    private static final IContent LEFT_BRACKET = new Text ("[", false);
+    private static final IContent RIGHT_BRACKET = new Text ("]", false);
+
+    private static final int MAX_DISPLAY_NAME_LENGTH = 80;
+    private static final int SRC_LINE_OFFSET = 4;
+    
+    
+    private static final String CSS_HEADER_FOOTER       = "hdft";
+    private static final String CSS_TITLE               = "tl";
+    private static final String CSS_NAV                 = "nv";
+    
+    private static final String CSS_COVERAGE_ZERO       = "z";
+    private static final String CSS_COVERAGE_PARTIAL    = "p";
+    private static final String CSS_COVERAGE_COMPLETE   = "c";
+    
+    private static final String DARKER_BACKGROUND   = "#F0F0F0";
+    private static final String TITLE_BACKGROUND    = "#6699CC";
+    private static final String NAV_BACKGROUND      = "#6633DD";
+    
+    private static final String CSS_INVISIBLE_TABLE     = "it";
+    
+    private static final String CSS_ITEM_NAME           = "in";
+    
+    private static final String CSS_CLASS_ITEM_SPECIAL  = "cis";
+    
+    private static final String CSS_SOURCE              = "s";
+    private static final String CSS_LINENUM             = "l";
+    
+    private static final String CSS_BOTTOM              = "bt";
+    private static final String CSS_ODDROW              = "o";
+    private static final String CSS_BLANK               = "b";
+    
+    private static final String CSS_DATA = "";
+    private static final String CSS_DATA_HIGHLIGHT = CSS_DATA + "h";
+    private static final String CSS_DATA_FIRST = CSS_DATA + "f";
+    private static final String CSS_DATA_HIGHLIGHT_FIRST = CSS_DATA + "hf";
+    private static final String CSS_HEADER = "";
+    private static final String CSS_HEADER_FIRST = CSS_HEADER + "f";
+    
+    private static final String CSS_CLS_NOLEFT          = "cn";
+    
+    // TODO: optimize this
+    
+    private static final String CSS =
+        " TABLE,TD,TH {border-style:solid; border-color:black;}" +
+        " TD,TH {background:white;margin:0;line-height:100%;padding-left:0.5em;padding-right:0.5em;}" +
+        
+        " TD {border-width:0 1px 0 0;}" +
+        " TH {border-width:1px 1px 1px 0;}" +
+        " TR TD." + CSS_DATA_HIGHLIGHT + " {color:red;}" +
+
+        " TABLE {border-spacing:0; border-collapse:collapse;border-width:0 0 1px 1px;}" +
+            
+        " P,H1,H2,H3,TH {font-family:verdana,arial,sans-serif;font-size:10pt;}" +
+        " TD {font-family:courier,monospace;font-size:10pt;}" + 
+         
+        " TABLE." + CSS_HEADER_FOOTER + " {border-spacing:0;border-collapse:collapse;border-style:none;}" + 
+        " TABLE." + CSS_HEADER_FOOTER + " TH,TABLE." + CSS_HEADER_FOOTER + " TD {border-style:none;line-height:normal;}" +
+        
+        " TABLE." + CSS_HEADER_FOOTER + " TH." + CSS_TITLE + ",TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_TITLE + " {background:" + TITLE_BACKGROUND + ";color:white;}" +
+        " TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_NAV + " {background:" + NAV_BACKGROUND + ";color:white;}" +
+                
+        " ." + CSS_NAV + " A:link {color:white;}" +
+        " ." + CSS_NAV + " A:visited {color:white;}" +
+        " ." + CSS_NAV + " A:active {color:yellow;}" +
+        
+        " TABLE." + CSS_HEADER_FOOTER + " A:link {color:white;}" +
+        " TABLE." + CSS_HEADER_FOOTER + " A:visited {color:white;}" +
+        " TABLE." + CSS_HEADER_FOOTER + " A:active {color:yellow;}" +
+        
+        //" ." + CSS_ITEM_NAME + " {color:#6633FF;}" + 
+        //" ." + CSS_ITEM_NAME + " {color:#C000E0;}" +
+        " ." + CSS_ITEM_NAME + " {color:#356085;}" +
+        
+        //" A:hover {color:#0066FF; text-decoration:underline; font-style:italic}" + 
+        
+        " TABLE." + CSS_SOURCE + " TD {padding-left:0.25em;padding-right:0.25em;}" +
+        " TABLE." + CSS_SOURCE + " TD." + CSS_LINENUM + " {padding-left:0.25em;padding-right:0.25em;text-align:right;background:" + DARKER_BACKGROUND + ";}" +
+        " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_ZERO + " TD {background:#FF9999;}" +
+        " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_PARTIAL + " TD {background:#FFFF88;}" +
+        " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_COMPLETE + " TD {background:#CCFFCC;}" +
+        
+        " A:link {color:#0000EE;text-decoration:none;}" + 
+        " A:visited {color:#0000EE;text-decoration:none;}" +
+        " A:hover {color:#0000EE;text-decoration:underline;}" +
+        
+        " TABLE." + CSS_CLS_NOLEFT + " {border-width:0 0 1px 0;}" +
+        " TABLE." + CSS_SOURCE + " {border-width:1px 0 1px 1px;}" +
+        
+//        " TD {border-width: 0px 1px 0px 0px; }" +
+        " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:0 1px 0 0;}" +
+        " TD." + CSS_DATA_FIRST + " {border-width:0 1px 0 1px;}" +
+        " TD." + CSS_DATA_HIGHLIGHT_FIRST + " {color:red;border-width:0 1px 0 1px;}" +
+
+//        " TH {border-width: 1px 1px 1px 0px; }" +        
+        " TH." + CSS_HEADER_FIRST + " {border-width:1px 1px 1px 1px;}" +
+
+        " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {background:" + DARKER_BACKGROUND + ";}" +
+        " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {border-width:1px 1px 1px 0;}" +
+        " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:1px 1px 1px 0;}" +        
+        " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_FIRST + " {border-width:1px 1px 1px 1px;}" +
+        " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT_FIRST + " {color:red;border-width:1px 1px 1px 1px;}" +
+        
+        " TD." + CSS_BLANK + " {border-style:none;background:transparent;line-height:50%;} " +         
+        " TD." + CSS_BOTTOM + " {border-width:1px 0 0 0;background:transparent;line-height:50%;}" +
+        " TR." + CSS_ODDROW + " TD {background:" + DARKER_BACKGROUND + ";}" +
+        
+        "TABLE." + CSS_INVISIBLE_TABLE + " {border-style:none;}" +
+        "TABLE." + CSS_INVISIBLE_TABLE + " TD,TABLE." + CSS_INVISIBLE_TABLE + " TH {border-style:none;}" +
+        
+        "";
+
+    private static final String NESTED_ITEMS_PARENT_DIRNAME = "_files";
+    private static final File NESTED_ITEMS_PARENT_DIR = new File (NESTED_ITEMS_PARENT_DIRNAME);
+    private static final int [][] NESTING; // set in <clinit>; this reflects the dir structure for the report
+    
+    private static final String FILE_EXTENSION = ".html";
+    private static final int IO_BUF_SIZE = 32 * 1024;
+    
+    private static final long [] ATTRIBUTE_SETS; // set in <clinit>
+    
+    static
+    {
+        final IItemMetadata [] allTypes = IItemMetadata.Factory.getAllTypes (); 
+        
+        ATTRIBUTE_SETS = new long [allTypes.length];
+        for (int t = 0; t < allTypes.length; ++ t)
+        {
+            ATTRIBUTE_SETS [allTypes [t].getTypeID ()] = allTypes [t].getAttributeIDs ();
+        }
+        
+        NESTING = new int [4][4];
+        
+        int base = AllItem.getTypeMetadata().getTypeID (); 
+        NESTING [base][PackageItem.getTypeMetadata ().getTypeID ()] = 1;
+        NESTING [base][SrcFileItem.getTypeMetadata ().getTypeID ()] = 1;
+        NESTING [base][ClassItem.getTypeMetadata ().getTypeID ()] = 1;
+        
+        base = PackageItem.getTypeMetadata().getTypeID ();
+        NESTING [base][AllItem.getTypeMetadata ().getTypeID ()] = -1;
+        
+        base = SrcFileItem.getTypeMetadata().getTypeID ();
+        NESTING [base][AllItem.getTypeMetadata ().getTypeID ()] = -1;
+        
+        base = ClassItem.getTypeMetadata().getTypeID ();
+        NESTING [base][AllItem.getTypeMetadata ().getTypeID ()] = -1;
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/emma/report/html/doc/Attribute.java b/core/java12/com/vladium/emma/report/html/doc/Attribute.java
new file mode 100644
index 0000000..148cba2
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/Attribute.java
@@ -0,0 +1,101 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Attribute.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Attribute implements IContent
+{
+    // public: ................................................................
+    
+    public static final Attribute ID = new AttributeImpl ("ID");
+    public static final Attribute NAME = new AttributeImpl ("NAME");
+    public static final Attribute TITLE = new AttributeImpl ("TITLE");
+    public static final Attribute TYPE = new AttributeImpl ("TYPE");
+    public static final Attribute CLASS = new AttributeImpl ("CLASS");
+    public static final Attribute HTTP_EQUIV = new AttributeImpl ("HTTP-EQUIV");
+    public static final Attribute CONTENT = new AttributeImpl ("CONTENT");
+    public static final Attribute HREF = new AttributeImpl ("HREF");
+    public static final Attribute SRC = new AttributeImpl ("SRC");
+    public static final Attribute REL = new AttributeImpl ("REL");
+    public static final Attribute WIDTH = new AttributeImpl ("WIDTH");
+    public static final Attribute SIZE = new AttributeImpl ("SIZE");
+    public static final Attribute BORDER = new AttributeImpl ("BORDER");
+    public static final Attribute CELLPADDING = new AttributeImpl ("CELLPADDING");
+    public static final Attribute CELLSPACING = new AttributeImpl ("CELLSPACING");
+    public static final Attribute ALIGN = new AttributeImpl ("ALIGN");
+    public static final Attribute COLSPAN = new AttributeImpl ("COLSPAN");
+    
+    public abstract String getName ();
+    
+    public abstract boolean equals (final Object rhs);
+    public abstract int hashCode ();
+    
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    Attribute () {}
+    
+    // private: ...............................................................
+    
+    
+    private static final class AttributeImpl extends Attribute
+    {
+        
+        public boolean equals (final Object rhs)
+        {
+            if (this == rhs) return true;
+            if (! (rhs instanceof AttributeImpl)) return false;
+            
+            return m_name.equals (((AttributeImpl) rhs).m_name); 
+        }
+        
+        public int hashCode ()
+        {
+            return m_name.hashCode ();
+        }
+        
+        public String toString ()
+        {
+            return m_name;
+        }
+        
+        public void emit (final HTMLWriter out)
+        {
+            out.write (m_name); // no need to escape anything
+        }
+        
+        public String getName ()
+        {
+            return m_name;
+        }
+        
+        
+        AttributeImpl (final String name)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (name != null, "name = null");
+            
+            m_name = name;
+        }
+
+        
+        private final String m_name;
+        
+    } // end of nested class 
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/AttributeSet.java b/core/java12/com/vladium/emma/report/html/doc/AttributeSet.java
new file mode 100644
index 0000000..f49040c
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/AttributeSet.java
@@ -0,0 +1,117 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AttributeSet.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.vladium.util.Strings;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class AttributeSet implements IContent
+{
+    // public: ................................................................
+    
+    public static AttributeSet create ()
+    {
+        return new AttributeSetImpl ();
+    }
+    
+    // ACCESSORS:
+    
+    public abstract boolean isEmpty ();
+    
+    // MUTATORS:
+    
+    public abstract AttributeSet set (Attribute attr, String value);
+    public abstract AttributeSet set (Attribute attr, int value);
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    AttributeSet () {}
+    
+    // private: ...............................................................
+    
+    
+    private static final class AttributeSetImpl extends AttributeSet
+    {
+        public void emit (final HTMLWriter out)
+        {
+            boolean first = true;
+            for (Iterator a = m_attrMap.entrySet ().iterator (); a.hasNext (); )
+            {
+                final Map.Entry entry = (Map.Entry) a.next ();
+                
+                final Attribute attr = (Attribute) entry.getKey ();
+                final String value = entry.getValue ().toString ();
+                
+                if (first)
+                    first = false;
+                else
+                    out.write (' ');
+                    
+                out.write (attr.getName ());
+                out.write ("=\"");
+
+                if ((m_buf != null) && (m_buf.length () <= MAX_BUF_LENGTH))
+                    m_buf.setLength (0);
+                else
+                    m_buf = new StringBuffer ();
+                
+                Strings.HTMLEscape (value, m_buf);
+                out.write (m_buf.toString ());
+                
+                out.write ('\"');
+            }
+        }
+        
+        public boolean isEmpty ()
+        {
+            return m_attrMap.isEmpty ();
+        }
+
+        
+        public AttributeSet set (final Attribute attr, final String value) // null removes?
+        {
+            m_attrMap.put (attr, value);
+            
+            return this;
+        }
+        
+        public AttributeSet set (final Attribute attr, final int value)
+        {
+            m_attrMap.put (attr, new Integer (value)); // TODO: use int factory here
+            
+            return this;
+        }
+        
+        
+        AttributeSetImpl ()
+        {
+            m_attrMap = new HashMap ();
+        }
+        
+        // TODO: consider lazy-initing this
+        private final Map /* Attribute->String|Integer */ m_attrMap; // never null
+        private StringBuffer m_buf; // reused by emit() 
+        
+        private static final int MAX_BUF_LENGTH = 4 * 1024;
+        
+    } // end of nested class
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/ElementList.java b/core/java12/com/vladium/emma/report/html/doc/ElementList.java
new file mode 100644
index 0000000..c7bb87c
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/ElementList.java
@@ -0,0 +1,77 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ElementList.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+// ----------------------------------------------------------------------------
+/**
+ * element list that is not necessarily an element itself
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ElementList implements IElementList
+{
+    // public: ................................................................
+
+
+    public ElementList ()
+    {
+        m_contents = new ArrayList ();
+    }
+
+    
+    public void emit (final HTMLWriter out)
+    {
+        for (Iterator c = m_contents.iterator (); c.hasNext (); )
+        {
+            final IContent content = (IContent) c.next ();
+            content.emit (out);
+        }
+    }
+                
+    public IElementList add (final IContent content)
+    {
+        if (content != null)
+        {
+            m_contents.add (content);
+        }
+        
+        return this;
+    }
+    
+    public IElementList add (final int index, final IContent content)
+    {
+        if (content != null)
+        {
+            m_contents.add (index, content);
+        }
+        
+        return this;
+    }
+    
+    public int size ()
+    {
+        return m_contents.size ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final List /* Content */ m_contents;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/HTMLDocument.java b/core/java12/com/vladium/emma/report/html/doc/HTMLDocument.java
new file mode 100644
index 0000000..7c2d27e
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/HTMLDocument.java
@@ -0,0 +1,200 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: HTMLDocument.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import com.vladium.util.IConstants;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class HTMLDocument extends IElement.Factory.ElementImpl
+{
+    // public: ................................................................
+    
+
+    public HTMLDocument ()
+    {
+        this (null, null);
+    }
+    
+    public HTMLDocument (final String title, final String encoding)
+    {
+        super (Tag.HTML, AttributeSet.create ());
+        
+        super.add (m_head = IElement.Factory.create (Tag.HEAD));
+        super.add (m_body = IElement.Factory.create (Tag.BODY));
+
+        // specify encoding META before anything else:
+        if ((encoding != null) && (encoding.length () != 0))
+        {
+            final ISimpleElement meta = ISimpleElement.Factory.create (Tag.META);
+            
+            meta.getAttributes ()
+            .set (Attribute.HTTP_EQUIV, "Content-Type")
+            .set (Attribute.CONTENT, "text/html; charset=" + encoding);
+            
+            m_head.add (meta);
+        }
+        
+        if (title != null)
+        {
+            // TODO: escape
+            //getAttributes ().set (Attribute.TITLE, title);
+            
+            final IElement titleElement = IElement.Factory.create (Tag.TITLE).setText (title, false);
+            m_head.add (titleElement);            
+        }
+        
+        m_title = title;
+    }
+    
+    public String getTitle ()
+    {
+        return m_title;
+    }
+    
+    public IElement getHead ()
+    {
+        return m_head; 
+    }
+    
+    public IElement getBody ()
+    {
+        return m_body;
+    }
+    
+    public IContent getHeader ()
+    {
+        return m_header;
+    }
+    
+    public IContent getFooter ()
+    {
+        return m_footer;
+    }
+
+
+    public void setHeader (final IContent header)
+    {
+        if (header != null) m_header = header;
+    }
+    
+    public void setFooter (final IContent footer)
+    {
+        if (footer != null) m_footer = footer;
+    }
+
+    /**
+     * Overridden to ensure header/footer appear first/last in the body.
+     */
+    public void emit (HTMLWriter out)
+    {
+        if (m_header != null) m_body.add (0, m_header);
+        if (m_footer != null) m_body.add (m_body.size (), m_footer);
+            
+        super.emit(out);
+    }
+
+    /**
+     * Overridden to add to the doc body.
+     */
+    public IElementList add (final IContent content)
+    {
+        m_body.add (content);
+        
+        return this;
+    }
+    
+    public void addStyle (final String css)
+    {
+        if (css != null)
+        {
+            final IElement style = IElement.Factory.create (Tag.STYLE);
+            style.getAttributes ().set (Attribute.TYPE, "text/css");
+            
+            final StringBuffer def = new StringBuffer ("<!--");
+            def.append (IConstants.EOL);
+            
+            style.setText (css, false);
+            
+            def.append (IConstants.EOL);
+            def.append ("-->");
+            
+            m_head.add (style);
+        }
+    }
+    
+    /**
+     * Adds a &lt;LINK&gt; to the head.
+     */
+    public void addLINK (final String type, final String href)
+    {
+        final ISimpleElement link = ISimpleElement.Factory.create (Tag.LINK);
+        
+        // TODO: add REL="STYLESHEET"
+        
+        link.getAttributes ().set (Attribute.TYPE, type); // TODO: escape
+        link.getAttributes ().set (Attribute.HREF, href); // TODO: escape
+        link.getAttributes ().set (Attribute.SRC, href); // TODO: escape
+        
+        m_head.add (link);
+    }
+    
+    public void addH (final int level, final String text, final String classID)
+    {
+        final Tag Hl = Tag.Hs [level];
+        
+        final IElement h = IElement.Factory.create (Hl);
+        h.setText (text, true);
+        h.setClass (classID);
+         
+        add (h);
+    }
+    
+    public void addH (final int level, final IContent text, final String classID)
+    {
+        final Tag Hl = Tag.Hs [level];
+        
+        final IElement h = IElement.Factory.create (Hl);
+        h.add (text);
+        h.setClass (classID);
+         
+        add (h);
+    }
+    
+    public void addHR (final int size)
+    {
+        final IElement hr = IElement.Factory.create (Tag.HR);
+        hr.getAttributes ().set (Attribute.SIZE, size);
+        
+        add (hr);
+    }
+    
+    public void addEmptyP ()
+    {
+        add (IElement.Factory.create (Tag.P));
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final String m_title;
+    private final IElement m_head;
+    private final IElement m_body;
+    
+    private IContent m_header, m_footer;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/HTMLTable.java b/core/java12/com/vladium/emma/report/html/doc/HTMLTable.java
new file mode 100644
index 0000000..70d5f83
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/HTMLTable.java
@@ -0,0 +1,132 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: HTMLTable.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class HTMLTable extends IElement.Factory.ElementImpl
+{
+    // public: ................................................................
+    
+    public static interface ICell extends IElement
+    {
+        ICell setColspan (final int span);
+        
+    } // end of nested interface
+    
+    public static interface IRow extends IElement
+    {
+        ICell newCell ();
+        
+    } // end of nested interface
+    
+    public HTMLTable (final String width, final String border, final String cellpadding, final String cellspacing)
+    {
+        super (Tag.TABLE, AttributeSet.create ());
+        
+        final AttributeSet attrs = getAttributes ();
+        
+        if (width != null) attrs.set (Attribute.WIDTH, width);
+        if (border != null) attrs.set (Attribute.BORDER, border);
+        if (cellpadding != null) attrs.set (Attribute.CELLPADDING, cellpadding);
+        if (cellspacing != null) attrs.set (Attribute.CELLSPACING, cellspacing);
+        
+        //m_rows = new LinkedList (); 
+    }
+    
+    public void setCaption (final String align, final String text, final boolean nbsp)
+    {
+        m_caption = IElement.Factory.create (Tag.CAPTION);
+        
+        m_caption.getAttributes ().set (Attribute.ALIGN, align);
+        m_caption.setText (text, nbsp);
+    }
+    
+    public IRow newTitleRow ()
+    {
+        final Row row = new Row (true);
+        add (row);
+        
+        return row;
+    }
+    
+    public IRow newRow ()
+    {
+        final Row row = new Row (false);
+        add (row);
+        
+        return row;
+    }
+    
+    public void emit (final HTMLWriter out)
+    {
+        if (m_caption != null)
+        {
+            add (0, m_caption);
+        }
+        
+        super.emit(out);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static class Cell extends IElement.Factory.ElementImpl
+                              implements ICell
+    {
+        public ICell setColspan (final int span)
+        {
+            getAttributes ().set (Attribute.COLSPAN, span);
+            
+            return this;
+        }
+        
+        Cell (Tag tag)
+        {
+            super (tag, AttributeSet.create ());
+        }
+                
+    } // end of nested class
+    
+    
+    private static class Row extends IElement.Factory.ElementImpl
+                             implements IRow
+    {
+        public ICell newCell ()
+        {
+            final ICell cell = new Cell (m_th ? Tag.TH : Tag.TD);
+            add (cell);
+            
+            return cell;
+        }
+        
+        Row (final boolean th)
+        {
+            super (Tag.TR, AttributeSet.create ());
+            
+            m_th = th;
+        }
+        
+        
+        private final boolean m_th;
+        
+    } // end of nested class
+    
+    
+    private IElement m_caption;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/HTMLWriter.java b/core/java12/com/vladium/emma/report/html/doc/HTMLWriter.java
new file mode 100644
index 0000000..d9298c8
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/HTMLWriter.java
@@ -0,0 +1,119 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: HTMLWriter.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import com.vladium.util.IConstants;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class HTMLWriter
+{
+    // public: ................................................................
+    
+    // TODO: add API for indenting
+    
+    public HTMLWriter (final Writer out)
+    {
+        if (out == null) throw new IllegalArgumentException ("null input: out");
+        
+        m_out = out;
+    }
+    
+    
+    public void write (final String s)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (s != null, "s = null");
+        
+        if (m_out != null)
+        {
+            try
+            {
+                m_out.write (s);
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+        }
+    }
+    
+    public void write (final char c)
+    {
+        if (m_out != null)
+        {
+            try
+            {
+                m_out.write (c);
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+        }
+    }
+    
+    public void eol ()
+    {
+        if (m_out != null)
+        {
+            try
+            {
+                m_out.write (IConstants.EOL);
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+        }
+    }
+    
+    public void flush ()
+    {
+        if (m_out != null)
+        {
+            try
+            {
+                m_out.flush ();
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+        }
+    }
+    
+    public void close ()
+    {
+        if (m_out != null)
+        {
+            try { m_out.close (); } catch (IOException ignore) {}
+            m_out = null;
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private Writer m_out;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/HyperRef.java b/core/java12/com/vladium/emma/report/html/doc/HyperRef.java
new file mode 100644
index 0000000..8a4584a
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/HyperRef.java
@@ -0,0 +1,42 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: HyperRef.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public class HyperRef extends IElement.Factory.ElementImpl
+{
+    // public: ................................................................
+    
+    public HyperRef (final String href, final String text, final boolean nbsp)
+    {
+        super (Tag.A, AttributeSet.create ());
+        
+        if ((href == null) || (href.length () == 0))
+            throw new IllegalArgumentException ("null or empty input: href");
+        
+        if ((text == null) || (text.length () == 0))
+            throw new IllegalArgumentException ("null or empty input: text");
+        
+        getAttributes ().set (Attribute.HREF, href);
+        
+        // TODO: does href need to be URL-encoded?
+        setText (text, nbsp); 
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/IContent.java b/core/java12/com/vladium/emma/report/html/doc/IContent.java
new file mode 100644
index 0000000..8ccaf40
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/IContent.java
@@ -0,0 +1,23 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IContent.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IContent
+{
+    // public: ................................................................
+    
+    void emit (final HTMLWriter out);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/IElement.java b/core/java12/com/vladium/emma/report/html/doc/IElement.java
new file mode 100644
index 0000000..a1e3ae0
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/IElement.java
@@ -0,0 +1,127 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IElement.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IElement extends ISimpleElement, IElementList
+{
+    // public: ................................................................
+    
+    IElement setText (String text, boolean nbsp); // size() is 0 after this
+    
+    abstract class Factory
+    {
+        public static IElement create (final Tag tag)
+        {
+            return new ElementImpl (tag, AttributeSet.create ());
+        }
+        
+        public static IElement create (final Tag tag, final AttributeSet attrs)
+        {
+            return new ElementImpl (tag, attrs);
+        }
+        
+        // TODO: should this extend ElementList?
+        static class ElementImpl extends ISimpleElement.Factory.SimpleElementImpl
+                                         implements IElement
+        {
+            public String toString ()
+            {
+                return "<" + m_tag.getName () + ">";
+            }
+            
+            public void emit (final HTMLWriter out)
+            {
+                final String tagName = m_tag.getName ();
+                
+                out.write ('<');
+                out.write (tagName);
+                
+                if (! m_attrs.isEmpty ())
+                {
+                    out.write (' ');
+                    m_attrs.emit (out);
+                }
+                
+                out.write ('>');
+                
+                for (Iterator c = m_contents.iterator (); c.hasNext (); )
+                {
+                    final IContent content = (IContent) c.next ();
+                    content.emit (out);
+                }
+                
+                out.write ("</");
+                out.write (tagName);
+                out.write ('>');
+                if (DEBUG_HTML) out.eol (); // using ENABLED as DEBUG here
+            }
+                        
+            public IElementList add (final IContent content)
+            {
+                if (content != null)
+                {
+                    m_contents.add (content);
+                }
+                
+                return this;
+            }
+            
+            public IElementList add (final int index, final IContent content)
+            {
+                if (content != null)
+                {
+                    m_contents.add (index, content);
+                }
+                
+                return this;
+            }
+            
+            public int size ()
+            {
+                return m_contents.size ();
+            }
+            
+            public IElement setText (final String text, final boolean nbsp)
+            {
+                if (text != null)
+                {
+                    m_contents.clear ();
+                    m_contents.add (new Text (text, nbsp));
+                }
+                
+                return this;
+            }
+            
+            ElementImpl (final Tag tag, final AttributeSet attrs)
+            {
+                super (tag, attrs);
+                
+                m_contents = new ArrayList ();
+            }
+
+            
+            protected final List /* Content */ m_contents;
+            
+            private static final boolean DEBUG_HTML = false;
+                    
+        } // end of nested class
+
+    } // end of nested class 
+    
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/IElementList.java b/core/java12/com/vladium/emma/report/html/doc/IElementList.java
new file mode 100644
index 0000000..4fa151b
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/IElementList.java
@@ -0,0 +1,31 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IElementList.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IElementList extends IContent
+{
+    // public: ................................................................
+    
+    IElementList add (IContent content);
+    IElementList add (int index, IContent content);
+    int size (); // element count 
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/ISimpleElement.java b/core/java12/com/vladium/emma/report/html/doc/ISimpleElement.java
new file mode 100644
index 0000000..6d6c440
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/ISimpleElement.java
@@ -0,0 +1,99 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ISimpleElement.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface ISimpleElement extends IContent
+{
+    // public: ................................................................
+
+    Tag getTag ();
+    ISimpleElement setClass (String classID);
+    AttributeSet getAttributes ();     
+    
+    abstract class Factory
+    {
+        public static ISimpleElement create (final Tag tag)
+        {
+            return new SimpleElementImpl (tag, AttributeSet.create ());
+        }
+        
+        public static ISimpleElement create (final Tag tag, final AttributeSet attrs)
+        {
+            return new SimpleElementImpl (tag, attrs);
+        }
+        
+        static class SimpleElementImpl implements ISimpleElement
+        {
+            public String toString ()
+            {
+                return "<" + m_tag.getName () + "/>";
+            }
+            
+            public Tag getTag ()
+            {
+                return m_tag;
+            }
+            
+            public ISimpleElement setClass (final String classID)
+            {
+                if ((classID != null) && (classID.length () > 0))
+                {
+                    getAttributes ().set (Attribute.CLASS, classID);
+                }
+                
+                return this;
+            }
+            
+            public AttributeSet getAttributes ()
+            {
+                return m_attrs;
+            }
+    
+            public void emit (final HTMLWriter out)
+            {
+                out.write ('<');
+                out.write (m_tag.getName ());
+                
+                if (! m_attrs.isEmpty ())
+                {
+                    out.write (' ');
+                    m_attrs.emit (out);
+                }
+                
+                out.write ("/>");
+            }
+            
+            SimpleElementImpl (final Tag tag, final AttributeSet attrs)
+            {
+                if ($assert.ENABLED) $assert.ASSERT (tag != null, "tag = null");
+                if ($assert.ENABLED) $assert.ASSERT (attrs != null, "attrs = null");
+                
+                m_tag = tag;
+                m_attrs = attrs;
+            }
+            
+            
+            protected final Tag m_tag;
+            protected final AttributeSet m_attrs;
+            
+        } // end of nested class
+        
+    } // end of nested class
+    
+
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/Tag.java b/core/java12/com/vladium/emma/report/html/doc/Tag.java
new file mode 100644
index 0000000..94614bb
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/Tag.java
@@ -0,0 +1,92 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Tag.java,v 1.1.1.1 2004/05/09 16:57:41 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Tag implements IContent
+{
+    // public: ................................................................
+    
+    public static final Tag HTML = new TagImpl ("HTML");
+    public static final Tag HEAD = new TagImpl ("HEAD");
+    public static final Tag BODY = new TagImpl ("BODY");
+    public static final Tag META = new TagImpl ("META");
+    public static final Tag STYLE = new TagImpl ("STYLE");
+    
+    public static final Tag TITLE = new TagImpl ("TITLE");
+    public static final Tag H1 = new TagImpl ("H1");
+    public static final Tag H2 = new TagImpl ("H2");
+    public static final Tag H3 = new TagImpl ("H3");
+    public static final Tag H4 = new TagImpl ("H4");
+    public static final Tag H5 = new TagImpl ("H5");
+    public static final Tag H6 = new TagImpl ("H6");
+    public static final Tag LINK = new TagImpl ("LINK");
+    
+    public static final Tag A = new TagImpl ("A");
+    
+    public static final Tag TABLE = new TagImpl ("TABLE");
+    public static final Tag CAPTION = new TagImpl ("CAPTION");
+    public static final Tag TH = new TagImpl ("TH");
+    public static final Tag TR = new TagImpl ("TR");
+    public static final Tag TD = new TagImpl ("TD");
+    
+    public static final Tag HR = new TagImpl ("HR");
+    public static final Tag P = new TagImpl ("P");
+    public static final Tag SPAN = new TagImpl ("SPAN");
+    
+    public static final Tag [] Hs = new Tag [] {H1, H2, H3, H4, H4, H6};
+    
+    public abstract String getName ();
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    Tag () {}
+    
+    // private: ...............................................................
+    
+    private static final class TagImpl extends Tag
+    {
+        public void emit (final HTMLWriter out)
+        {
+            out.write (m_name);
+        }
+        
+        public String getName ()
+        {
+            return m_name;
+        }
+        
+        public String toString ()
+        {
+            return m_name;
+        }
+        
+        TagImpl (final String name)
+        {
+            if ($assert.ENABLED) $assert.ASSERT (name != null, "name = null");
+            
+            m_name = name;
+        }
+        
+        
+        private final String m_name;
+        
+    } // end of 
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/Text.java b/core/java12/com/vladium/emma/report/html/doc/Text.java
new file mode 100644
index 0000000..5dc731e
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/Text.java
@@ -0,0 +1,50 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Text.java,v 1.1.1.1 2004/05/09 16:57:42 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+import com.vladium.util.Strings;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public 
+final class Text implements IContent
+{
+    // public: ................................................................
+
+    public Text (final String text, final boolean nbsp)
+    {
+        m_text = text;
+        m_nbsp = nbsp;
+    }
+    
+    public void emit (final HTMLWriter out)
+    {
+        if (m_text != null)
+        {
+            if (m_nbsp)
+                out.write (Strings.HTMLEscapeSP (m_text));
+            else
+                out.write (Strings.HTMLEscape (m_text));
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final String m_text;
+    private final boolean m_nbsp;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/html/doc/TextContent.java b/core/java12/com/vladium/emma/report/html/doc/TextContent.java
new file mode 100644
index 0000000..53c851c
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/html/doc/TextContent.java
@@ -0,0 +1,43 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: TextContent.java,v 1.1.1.1 2004/05/09 16:57:42 vlad_r Exp $
+ */
+package com.vladium.emma.report.html.doc;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class TextContent implements IContent
+{
+    // public: ................................................................
+
+    public TextContent (final String text)
+    {
+        m_text = text;
+    }   
+
+    public void emit (final HTMLWriter out)
+    {
+        if (m_text != null)
+        {
+            out.write (m_text);
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final String m_text;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/reportCommand.java b/core/java12/com/vladium/emma/report/reportCommand.java
new file mode 100644
index 0000000..5810657
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/reportCommand.java
@@ -0,0 +1,175 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: reportCommand.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.report;
+
+import java.io.IOException;
+
+import com.vladium.util.ClassLoaderResolver;
+import com.vladium.util.Strings;
+import com.vladium.util.args.IOptsParser;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.Command;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class reportCommand extends Command
+{
+    // public: ................................................................
+
+    public reportCommand (final String usageToolName, final String [] args)
+    {
+        super (usageToolName, args);
+    }
+
+    public synchronized void run ()
+    {
+        ClassLoader loader;
+        try
+        {
+            loader = ClassLoaderResolver.getClassLoader ();
+        }
+        catch (Throwable t)
+        {
+            loader = getClass ().getClassLoader ();
+        }
+        
+        try
+        {
+            // process 'args':
+            {
+                final IOptsParser parser = getOptParser (loader);
+                final IOptsParser.IOpts parsedopts = parser.parse (m_args);
+                
+                // check if usage is requested before checking args parse errors etc:
+                {
+                    final int usageRequestLevel = parsedopts.usageRequestLevel ();
+
+                    if (usageRequestLevel > 0)
+                    {
+                        usageexit (parser, usageRequestLevel, null);
+                        return;
+                    }
+                }
+                
+                final IOptsParser.IOpt [] opts = parsedopts.getOpts ();
+                
+                if (opts == null) // this means there were args parsing errors
+                {
+                    parsedopts.error (m_out, STDOUT_WIDTH);
+                    usageexit (parser, IOptsParser.SHORT_USAGE, null);
+                    return;
+                }
+                
+                // process parsed args:
+
+                try
+                {
+                    for (int o = 0; o < opts.length; ++ o)
+                    {
+                        final IOptsParser.IOpt opt = opts [o];
+                        final String on = opt.getCanonicalName ();
+                        
+                        if (! processOpt (opt))
+                        {
+                            if ("in".equals (on))
+                            {
+                                m_datapath = getListOptValue (opt, PATH_DELIMITERS, true);
+                            }
+                            else if ("sp".equals (on))
+                            {
+                                m_srcpath = getListOptValue (opt, PATH_DELIMITERS, true);
+                            }
+                            else if ("r".equals (on))
+                            {
+                                m_reportTypes = Strings.merge (opt.getValues (), COMMA_DELIMITERS, true);
+                            }
+                        }
+                    }
+                    
+                    // user '-props' file property overrides:
+                    
+                    if (! processFilePropertyOverrides ()) return;
+                    
+                    // process prefixed opts:
+                    
+                    processCmdPropertyOverrides (parsedopts);
+                }
+                catch (IOException ioe)
+                {
+                    throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
+                }
+                
+                // handle cmd line-level defaults:
+                {
+                }
+            }
+            
+            // run the reporter:
+            {
+                final ReportProcessor processor = ReportProcessor.create ();
+                processor.setAppName (IAppConstants.APP_NAME); // for log prefixing
+                
+                processor.setDataPath (m_datapath);
+                processor.setSourcePath (m_srcpath);
+                if ($assert.ENABLED) $assert.ASSERT (m_reportTypes != null, "m_reportTypes no set");
+                processor.setReportTypes (m_reportTypes); // no "txt" default for report processor
+                processor.setPropertyOverrides (m_propertyOverrides);
+                
+                processor.run ();
+            }
+        }
+        catch (EMMARuntimeException yre)
+        {
+            // TODO: see below
+            
+            exit (true, yre.getMessage (), yre, RC_UNEXPECTED); // does not return
+            return;
+        }
+        catch (Throwable t)
+        {
+            // TODO: embed: OS/JVM fingerprint, build #, etc
+            // TODO: save stack trace in a file and prompt user to send it to ...
+            
+            exit (true, "unexpected failure: ", t, RC_UNEXPECTED); // does not return
+            return;
+        }
+
+        exit (false, null, null, RC_OK);
+    }    
+    
+    // protected: .............................................................
+
+
+    protected void initialize ()
+    {
+        super.initialize ();                
+    }
+    
+    protected String usageArgsMsg ()
+    {
+        return "[options]";
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private String [] m_datapath; // list of data files, not a real path
+    private String [] m_srcpath;
+    private String [] m_reportTypes;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/txt/ReportGenerator.java b/core/java12/com/vladium/emma/report/txt/ReportGenerator.java
new file mode 100644
index 0000000..aba41f9
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/txt/ReportGenerator.java
@@ -0,0 +1,528 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportGenerator.java,v 1.1.1.1.2.1 2004/07/16 23:32:30 vlad_r Exp $
+ */
+package com.vladium.emma.report.txt;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.vladium.util.Files;
+import com.vladium.util.IProperties;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.report.AbstractReportGenerator;
+import com.vladium.emma.report.AllItem;
+import com.vladium.emma.report.ClassItem;
+import com.vladium.emma.report.IItem;
+import com.vladium.emma.report.IItemAttribute;
+import com.vladium.emma.report.ItemComparator;
+import com.vladium.emma.report.MethodItem;
+import com.vladium.emma.report.PackageItem;
+import com.vladium.emma.report.SourcePathCache;
+import com.vladium.emma.report.SrcFileItem;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ReportGenerator extends AbstractReportGenerator
+                            implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    // TODO: this is prototype quality, needs major cleanup
+    
+    // IReportGenerator:
+    
+    public String getType ()
+    {
+        return TYPE;
+    }
+    
+    public void process (final IMetaData mdata, final ICoverageData cdata,
+                         final SourcePathCache cache, final IProperties properties)
+        throws EMMARuntimeException
+    {
+        initialize (mdata, cdata, cache, properties);
+        
+        long start = 0, end;
+        final boolean trace1 = m_log.atTRACE1 ();
+        
+        if (trace1) start = System.currentTimeMillis ();
+        
+        {
+            m_queue = new LinkedList ();
+            for (m_queue.add (m_view.getRoot ()); ! m_queue.isEmpty (); )
+            {
+                final IItem head = (IItem) m_queue.removeFirst ();
+                
+                head.accept (this, null);
+            }
+            line ();
+            
+            close ();
+        }
+                
+        if (trace1)
+        {
+            end = System.currentTimeMillis ();
+            
+            m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms");
+        }
+    }
+    
+    public void cleanup ()
+    {
+        m_queue = null;
+        close ();
+        
+        super.cleanup ();
+    }
+    
+    
+    // IItemVisitor:
+    
+    public Object visit (final AllItem item, final Object ctx)
+    {
+        File outFile = m_settings.getOutFile ();
+        if (outFile == null)
+        {
+            outFile = new File ("coverage.txt");
+            m_settings.setOutFile (outFile);
+        }
+        
+        final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile);
+        
+        m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ...");
+        
+        openOutFile (fullOutFile, m_settings.getOutEncoding (), true);
+        
+        // build ID stamp:
+        try
+        {
+            final StringBuffer label = new StringBuffer (101);
+            
+            label.append ("[");
+            label.append (IAppConstants.APP_NAME);
+            label.append (" v"); label.append (IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG);
+            label.append (" report, generated ");
+            label.append (new Date (EMMAProperties.getTimeStamp ()));
+            label.append ("]");
+            
+            m_out.write (label.toString ());
+            m_out.newLine ();
+            
+            m_out.flush ();
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+        
+        final int [] columns = m_settings.getColumnOrder ();
+         
+        line ();
+                
+        // [all] coverage summary row:
+        addTitleRow ("OVERALL COVERAGE SUMMARY", 0, 1);            
+        {
+            // header row:
+            addHeaderRow (item, columns);
+            
+            // coverage row:
+            addItemRow (item, columns);
+        }
+                
+        // [all] stats summary table ([all] only):
+        addTitleRow ("OVERALL STATS SUMMARY", 1, 1);
+        {
+            row ("total packages:" + m_separator + item.getChildCount ());
+            row ("total classes:" + m_separator + item.getAggregate (IItem.TOTAL_CLASS_COUNT)); 
+            row ("total methods:" + m_separator + item.getAggregate (IItem.TOTAL_METHOD_COUNT));
+            
+            if (m_srcView && m_hasSrcFileInfo)
+            {
+                row ("total executable files:" + m_separator + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT));
+                
+                if (m_hasLineNumberInfo)
+                    row ("total executable lines:" + m_separator + item.getAggregate (IItem.TOTAL_LINE_COUNT));
+            }
+        }
+        
+        final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
+        
+        // render package summary rows:
+        addTitleRow ("COVERAGE BREAKDOWN BY PACKAGE", 1, 1);
+        {
+            boolean headerDone = false;
+            final ItemComparator order = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()];                
+            for (Iterator packages = item.getChildren (order); packages.hasNext (); )
+            {
+                final IItem pkg = (IItem) packages.next ();
+                
+                if (! headerDone)
+                {
+                    // header row:
+                    addHeaderRow (pkg, columns);                        
+                    headerDone = true;
+                }
+                
+                // coverage row:
+                addItemRow (pkg, columns);
+                
+                if (deeper) m_queue.addLast (pkg);
+            }
+        }
+
+        return ctx;
+    }
+    
+    public Object visit (final PackageItem item, final Object ctx)
+    {
+        if (m_verbose) m_log.verbose ("  report: processing package [" + item.getName () + "] ...");
+        
+        final int [] columns = m_settings.getColumnOrder ();
+        
+        line ();
+        
+        // coverage summary row:
+        addTitleRow ("COVERAGE SUMMARY FOR PACKAGE [".concat (item.getName ()).concat ("]"), 0, 1);            
+        {
+            // header row:
+            addHeaderRow (item, columns);
+            
+            // coverage row:
+            addItemRow (item, columns);
+        }
+        
+        
+        final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
+                
+        // render child summary rows:
+        
+        final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE" : "COVERAGE BREAKDOWN BY CLASS";
+        addTitleRow (summaryTitle, 1, 1);
+        {
+            boolean headerDone = false;
+            final ItemComparator order = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()];                
+            for (Iterator srcORclsFiles = item.getChildren (order); srcORclsFiles.hasNext (); )
+            {
+                final IItem srcORcls = (IItem) srcORclsFiles.next ();
+                
+                if (! headerDone)
+                {
+                    // header row:
+                    addHeaderRow (srcORcls, columns);                        
+                    headerDone = true;
+                }
+                
+                // coverage row:
+                addItemRow (srcORcls, columns);
+                
+                if (deeper) m_queue.addLast (srcORcls);
+            }
+        }
+
+        return ctx;
+    }
+
+    public Object visit (final SrcFileItem item, final Object ctx)
+    {
+        final int [] columns = m_settings.getColumnOrder ();
+        
+        line ();
+        
+        // coverage summary row:
+        addTitleRow ("COVERAGE SUMMARY FOR SOURCE FILE [".concat (item.getName ()).concat ("]"), 0, 1);            
+        {
+            // header row:
+            addHeaderRow (item, columns);
+            
+            // coverage row:
+            addItemRow (item, columns);
+        }
+        
+        // render child summary rows:
+        addTitleRow ("COVERAGE BREAKDOWN BY CLASS AND METHOD", 1, 1);
+        {
+            boolean headerDone = false;
+            final ItemComparator order = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()];                
+            for (Iterator classes = item.getChildren (order); classes.hasNext (); )
+            {
+                final IItem cls = (IItem) classes.next ();
+                
+                if (! headerDone)
+                {
+                    // header row:
+                    addHeaderRow (cls, columns);                        
+                    headerDone = true;
+                }
+                
+                // coverage row:
+                //addItemRow (child, columns);
+                
+                addTitleRow ("class [" + cls.getName () + "] methods", 0, 0);
+                
+                // TODO: select the right comparator here
+                final ItemComparator order2 = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];                
+                for (Iterator methods = cls.getChildren (order2); methods.hasNext (); )
+                {
+                    final MethodItem method = (MethodItem) methods.next ();
+                    
+                    addItemRow (method, columns);
+                }
+            }
+        }
+
+        return ctx;
+    }
+    
+    public Object visit (final ClassItem item, final Object ctx)
+    {
+        final int [] columns = m_settings.getColumnOrder ();
+        
+        line ();
+        
+        // coverage summary row:
+        addTitleRow ("COVERAGE SUMMARY FOR CLASS [".concat (item.getName ()).concat ("]"), 0, 1);            
+        {
+            // header row:
+            addHeaderRow (item, columns);
+            
+            // coverage row:
+            addItemRow (item, columns);
+        }
+        
+        // render child summary rows:
+        addTitleRow ("COVERAGE BREAKDOWN BY METHOD", 1, 1);
+        {
+            final ItemComparator order = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];                
+            for (Iterator methods = item.getChildren (order); methods.hasNext (); )
+            {
+                final IItem method = (IItem) methods.next ();
+                
+                // coverage row:
+                addItemRow (method, columns);                
+            }
+        }
+
+        return ctx;
+    }
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private void addTitleRow (final String text, final int hlines, final int flines)
+    {
+        for (int i = 0; i < hlines; ++ i) eol ();
+        row (new StringBuffer (text).append (":"));
+        for (int i = 0; i < flines; ++ i) eol ();
+    } 
+    
+    private void addHeaderRow (final IItem item, final int [] columns)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (columns != null, "null input: columns");
+        }
+        
+        // header row:
+        final StringBuffer buf = new StringBuffer ();
+        
+        for (int c = 0, cLimit = columns.length; c < cLimit; ++ c)
+        {
+            final int attrID = columns [c];
+            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+            
+            if (attr != null)
+            {
+                buf.append ('[');
+                buf.append (attr.getName ());
+                buf.append (']');
+            }
+            if (c != cLimit - 1) buf.append (m_separator);
+        }
+        
+        row (buf);
+    }
+    
+    
+    /*
+     * No header row, just data rows.
+     */
+    private void addItemRow (final IItem item, final int [] columns)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (item != null, "null input: item");
+            $assert.ASSERT (columns != null, "null input: columns");
+        }
+        
+        final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer
+        
+        for (int c = 0, cLimit = columns.length; c < cLimit; ++ c)
+        {
+            final int attrID = columns [c];
+            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+            
+            if (attr != null)
+            {
+                boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+                
+                if (fail)
+                {
+                    //buf.append ('(');
+                    //buf.append ("! ");
+                    attr.format (item, buf);
+                    buf.append ('!');
+                    //buf.append (')');
+                }
+                else
+                {
+                    attr.format (item, buf);
+                }
+            }
+            if (c != cLimit - 1) buf.append (m_separator);
+        }
+        
+        row (buf);
+    }
+    
+    
+    private void row (final StringBuffer str)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (str != null, "str = null");
+        
+        try
+        {
+            m_out.write (str.toString ());
+            m_out.newLine ();
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+    }
+    
+    private void row (final String str)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (str != null, "str = null");
+        
+        try
+        {
+            m_out.write (str);
+            m_out.newLine ();
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+    }
+    
+    private void line ()
+    {
+        try
+        {
+            m_out.write (LINE);
+            m_out.newLine ();
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+    }
+    
+    private void eol ()
+    {
+        try
+        {
+            m_out.newLine ();
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+    }
+    
+    private void close ()
+    {
+        if (m_out != null)
+        {
+            try
+            {
+                m_out.flush ();
+                m_out.close ();
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+            finally
+            {
+                m_out = null;
+            }
+        }
+    }
+    
+    private void openOutFile (final File file, final String encoding, final boolean mkdirs)
+    {
+        try
+        {
+            if (mkdirs)
+            {
+                final File parent = file.getParentFile ();
+                if (parent != null) parent.mkdirs ();
+            }
+            
+            m_out = new BufferedWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE);
+        }
+        catch (UnsupportedEncodingException uee)
+        {
+            // TODO: error code
+            throw new EMMARuntimeException (uee);
+        }
+        // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
+        // was narrowed to FileNotFoundException:
+        catch (IOException fnfe) // FileNotFoundException
+        {
+            // TODO: error code
+            throw new EMMARuntimeException (fnfe);
+        }
+    }
+    
+    
+    private char m_separator = '\t'; // TODO: set this
+    
+    private LinkedList /* IITem */ m_queue;
+    private BufferedWriter m_out;
+    
+    private static final String TYPE = "txt";
+    private static final String LINE = "-------------------------------------------------------------------------------";
+    
+    private static final int IO_BUF_SIZE = 32 * 1024;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/report/xml/ReportGenerator.java b/core/java12/com/vladium/emma/report/xml/ReportGenerator.java
new file mode 100644
index 0000000..70be06e
--- /dev/null
+++ b/core/java12/com/vladium/emma/report/xml/ReportGenerator.java
@@ -0,0 +1,538 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ReportGenerator.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.emma.report.xml;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Date;
+import java.util.Iterator;
+
+import com.vladium.util.Files;
+import com.vladium.util.IConstants;
+import com.vladium.util.IProperties;
+import com.vladium.util.Strings;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.report.AbstractReportGenerator;
+import com.vladium.emma.report.AllItem;
+import com.vladium.emma.report.ClassItem;
+import com.vladium.emma.report.IItem;
+import com.vladium.emma.report.IItemAttribute;
+import com.vladium.emma.report.IItemMetadata;
+import com.vladium.emma.report.ItemComparator;
+import com.vladium.emma.report.MethodItem;
+import com.vladium.emma.report.PackageItem;
+import com.vladium.emma.report.SourcePathCache;
+import com.vladium.emma.report.SrcFileItem;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ReportGenerator extends AbstractReportGenerator
+                            implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    // IReportGenerator:
+    
+    public String getType ()
+    {
+        return TYPE;
+    }
+    
+    public void process (final IMetaData mdata, final ICoverageData cdata,
+                         final SourcePathCache cache, final IProperties properties)
+        throws EMMARuntimeException
+    {
+        initialize (mdata, cdata, cache, properties);
+        
+        long start = 0, end;
+        final boolean trace1 = m_log.atTRACE1 ();
+        
+        if (trace1) start = System.currentTimeMillis ();
+        
+        {
+            m_view.getRoot ().accept (this, null);
+            close ();
+        }
+                
+        if (trace1)
+        {
+            end = System.currentTimeMillis ();
+            
+            m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms");
+        }
+    }
+    
+    public void cleanup ()
+    {
+        close ();
+        
+        super.cleanup ();
+    }
+    
+    
+    // IItemVisitor:
+    
+    public Object visit (final AllItem item, final Object ctx)
+    {
+        try
+        {
+            File outFile = m_settings.getOutFile ();
+            if (outFile == null)
+            {
+                outFile = new File ("coverage.xml");
+                m_settings.setOutFile (outFile);
+            }
+            
+            final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile);
+            
+            m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ...");
+            
+            openOutFile (fullOutFile, m_settings.getOutEncoding (), true);
+            
+            // XML header:
+            m_out.write ("<?xml version=\"1.0\" encoding=\"" + m_settings.getOutEncoding () + "\"?>");
+            
+            // build ID stamp:
+            try
+            {
+                final StringBuffer label = new StringBuffer (101);
+                
+                label.append ("<!-- ");
+                label.append (IAppConstants.APP_NAME);
+                label.append (" v"); label.append (IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG);
+                label.append (" report, generated ");
+                label.append (new Date (EMMAProperties.getTimeStamp ()));
+                label.append (" -->");
+                
+                m_out.write (label.toString ());
+                m_out.newLine ();
+                
+                m_out.flush ();
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+            
+            eol ();
+            openElementTag ("report");
+            closeElementTag (false);
+            m_out.incIndent ();
+            
+            // stats summary section:
+            eol ();
+            openElementTag ("stats");
+            closeElementTag (false);
+            m_out.incIndent ();
+            {
+                emitStatsCount ("packages", item.getChildCount ());
+                emitStatsCount ("classes", item.getAggregate (IItem.TOTAL_CLASS_COUNT));
+                emitStatsCount ("methods", item.getAggregate (IItem.TOTAL_METHOD_COUNT));
+                
+                if (m_srcView && m_hasSrcFileInfo)
+                {
+                    emitStatsCount ("srcfiles", item.getAggregate (IItem.TOTAL_SRCFILE_COUNT));
+                    
+                    if (m_hasLineNumberInfo)
+                        emitStatsCount ("srclines", item.getAggregate (IItem.TOTAL_LINE_COUNT));
+                }
+            }
+            m_out.decIndent ();
+            eol ();
+            endElement ("stats");
+            
+            // actual coverage data:
+            eol ();
+            openElementTag ("data");
+            closeElementTag (false);
+            m_out.incIndent ();
+            {
+                final ItemComparator childrenOrder = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()];
+                emitItem (item, childrenOrder);
+            }
+            m_out.decIndent ();
+            eol ();
+            endElement ("data");
+            
+            m_out.decIndent ();
+            eol ();
+            endElement ("report");            
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+
+        return ctx;
+    }
+    
+    
+    public Object visit (final PackageItem item, final Object ctx)
+    {
+        if (m_verbose) m_log.verbose ("  report: processing package [" + item.getName () + "] ...");
+        
+        try
+        {
+            final ItemComparator childrenOrder = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()];
+            emitItem (item, childrenOrder);
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+
+        return ctx;
+    }
+    
+    
+    public Object visit (final SrcFileItem item, final Object ctx)
+    {
+        try
+        {
+            final ItemComparator childrenOrder = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()];
+            emitItem (item, childrenOrder);
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+
+        return ctx;
+    }
+
+    public Object visit (final ClassItem item, final Object ctx)
+    {
+        try
+        {
+            final ItemComparator childrenOrder = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];
+            emitItem (item, childrenOrder);
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+
+        return ctx;
+    }
+    
+    public Object visit (final MethodItem item, final Object ctx)
+    {
+        try
+        {
+            emitItem (item, null);
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+        }
+
+        return ctx;
+    }
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class IndentingWriter extends BufferedWriter
+    {
+        public void newLine () throws IOException
+        {
+            m_state = 0;
+            super.write (IConstants.EOL, 0, IConstants.EOL.length ());
+        }
+                
+        public void write (final char [] cbuf, final int off, final int len) throws IOException
+        {
+            indent ();
+            super.write (cbuf, off, len);
+        }
+
+        public void write (int c) throws IOException
+        {
+            indent ();
+            super.write (c);
+        }
+
+        public void write (final String s, final int off, final int len) throws IOException
+        {
+            indent ();
+            super.write (s, off, len);
+        }
+
+        
+        IndentingWriter (final Writer out, final int buffer, final int indent)
+        {
+            super (out, buffer);
+            m_indent = indent;
+        }
+
+        
+        void incIndent (final int delta)
+        {
+            if (delta < 0) throw new IllegalArgumentException ("delta be non-negative: " + delta);
+            
+            m_indent += delta;
+        }
+        
+        void incIndent ()
+        {
+            incIndent (INDENT_INCREMENT);
+        }
+        
+        void decIndent (final int delta)
+        {
+            if (delta < 0) throw new IllegalArgumentException ("delta be non-negative: " + delta);
+            if (delta > m_indent) throw new IllegalArgumentException ("delta = " + delta + ", current indent = " + m_indent);
+            
+            m_indent -= delta;
+        }
+        
+        void decIndent ()
+        {
+            decIndent (INDENT_INCREMENT);
+        }
+        
+        String getIndent ()
+        {
+            if (m_indent <= 0)
+                return "";
+            else
+            {
+                if ((m_sindent == null) || (m_sindent.length () < m_indent))
+                {
+                    final char [] ca = new char [m_indent];
+                    
+                    for (int i = 0; i < m_indent; ++ i) ca [i] = ' ';
+                    m_sindent = new String (ca);
+                    
+                    return m_sindent;
+                }
+                else
+                {
+                    return m_sindent.substring (0, m_indent);
+                }
+            }
+        }
+        
+                
+        private void indent ()
+            throws IOException
+        {
+            if (m_state == 0)
+            {
+                final String indent = getIndent ();
+                super.write (indent, 0, indent.length ());
+                
+                m_state = 1;
+            }
+        }
+        
+        
+        private int m_indent;
+        private int m_state;
+        private transient String m_sindent;
+        
+        private static final int INDENT_INCREMENT = 2;
+        
+    } // end of nested class
+    
+    
+    private void emitStatsCount (final String name, final int value)
+        throws IOException
+    {
+        eol ();
+        openElementTag (name);
+        m_out.write (" value=\"" + value);
+        m_out.write ('"');
+        closeElementTag (true);
+    } 
+
+    private void emitItem (final IItem item, final ItemComparator childrenOrder)
+        throws IOException
+    {        
+        final IItemMetadata metadata = item.getMetadata (); 
+        final int [] columns = m_settings.getColumnOrder ();            
+        final String tag = metadata.getTypeName ();
+ 
+        eol ();
+        
+        // emit opening tag with name attribute:
+        {
+            openElementTag (tag);
+            
+            m_out.write (" name=\"");
+            m_out.write (Strings.HTMLEscape (item.getName ()));
+            m_out.write ('"');
+            
+            closeElementTag (false);
+        }
+        
+        eol ();
+            
+        m_out.incIndent ();       
+
+        emitItemCoverage (item, columns);
+        
+        final boolean deeper = (childrenOrder != null) && (m_settings.getDepth () > metadata.getTypeID ()) && (item.getChildCount () > 0);
+        
+        if (deeper)
+        {
+            for (Iterator packages = item.getChildren (childrenOrder); packages.hasNext (); )
+            {
+                ((IItem) packages.next ()).accept (this, null);
+            }
+            
+            eol ();
+        }
+
+        m_out.decIndent ();
+        
+        // emit closing tag:
+        {
+            endElement (tag);
+        }
+    }
+    
+    /*
+     * No header row, just data rows.
+     */
+    private void emitItemCoverage (final IItem item, final int [] columns)
+        throws IOException
+    {
+        final StringBuffer buf = new StringBuffer (64);
+        
+        for (int c = 0, cLimit = columns.length; c < cLimit; ++ c)
+        {
+            final int attrID = columns [c];
+            
+            if (attrID != IItemAttribute.ATTRIBUTE_NAME_ID)
+            {
+                final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
+                
+                if (attr != null)
+                {
+                    openElementTag ("coverage");
+
+                    m_out.write (" type=\"");
+                    m_out.write (Strings.HTMLEscape (attr.getName ()));
+                    m_out.write ("\" value=\"");
+                    attr.format (item, buf);
+                    m_out.write (Strings.HTMLEscape (buf.toString ()));
+                    m_out.write ('"');
+                    buf.setLength (0);
+                    
+                    closeElementTag (true);
+                    
+                    eol ();
+                }
+            }
+        }
+        
+    }
+    
+    private void openElementTag (final String tag)
+        throws IOException
+    {
+        m_out.write ('<');
+        m_out.write (tag);
+    }
+    
+    private void closeElementTag (final boolean simple)
+        throws IOException
+    {
+        if (simple)
+            m_out.write ("/>");
+        else
+            m_out.write ('>');
+    }
+    
+    private void endElement (final String tag)
+        throws IOException
+    {
+        m_out.write ("</");
+        m_out.write (tag);
+        m_out.write ('>');
+    }
+    
+    private void eol ()
+        throws IOException
+    {
+        m_out.newLine ();
+    }
+    
+    private void close ()
+    {
+        if (m_out != null)
+        {
+            try
+            {
+                m_out.flush ();
+                m_out.close ();
+            }
+            catch (IOException ioe)
+            {
+                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
+            }
+            finally
+            {
+                m_out = null;
+            }
+        }
+    }
+    
+    private void openOutFile (final File file, final String encoding, final boolean mkdirs)
+    {
+        try
+        {
+            if (mkdirs)
+            {
+                final File parent = file.getParentFile ();
+                if (parent != null) parent.mkdirs ();
+            }
+            
+            m_out = new IndentingWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE, 0);
+        }
+        catch (UnsupportedEncodingException uee)
+        {
+            // TODO: error code
+            throw new EMMARuntimeException (uee);
+        }
+        // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
+        // was narrowed to FileNotFoundException:
+        catch (IOException fnfe) // FileNotFoundException
+        {
+            // TODO: error code
+            throw new EMMARuntimeException (fnfe);
+        }
+    }
+    
+    
+    private IndentingWriter m_out;
+    
+    private static final String TYPE = "xml";    
+    private static final int IO_BUF_SIZE = 64 * 1024;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/AppRunner.java b/core/java12/com/vladium/emma/rt/AppRunner.java
new file mode 100644
index 0000000..ad9fd1f
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/AppRunner.java
@@ -0,0 +1,964 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AppRunner.java,v 1.1.1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.Files;
+import com.vladium.util.IConstants;
+import com.vladium.util.IProperties;
+import com.vladium.util.Property;
+import com.vladium.util.SoftValueMap;
+import com.vladium.util.Strings;
+import com.vladium.util.asserts.$assert;
+import com.vladium.util.exception.Exceptions;
+import com.vladium.util.exit.ExitHookManager;
+import com.vladium.emma.AppLoggers;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.Processor;
+import com.vladium.emma.filter.IInclExclFilter;
+import com.vladium.emma.data.CoverageOptionsFactory;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.DataFactory;
+import com.vladium.emma.data.ISessionData;
+import com.vladium.emma.data.SessionData;
+import com.vladium.emma.report.AbstractReportGenerator;
+import com.vladium.emma.report.IReportGenerator;
+import com.vladium.emma.report.SourcePathCache;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class AppRunner extends Processor
+                      implements IAppErrorCodes
+{
+    // public: ................................................................
+    
+    
+    public static AppRunner create (final ClassLoader delegate)
+    {
+        return new AppRunner (delegate);
+    }
+    
+    
+    public synchronized void run ()
+    {
+        validateState ();
+        
+        // disable Runtime's own exit hook:
+        RTSettings.setStandaloneMode (false); // an optimization to disable RT's static init code [this line must precede any reference to RT]
+        RT.reset (true, false); // reset RT [RT creates 'cdata' and loads app properties]
+
+        // load tool properties:
+        final IProperties toolProperties;
+        {
+            IProperties appProperties = RT.getAppProperties (); // try to use app props consistent with RT's view of them
+            if (appProperties == null) appProperties = EMMAProperties.getAppProperties (); // don't use combine()
+            
+            toolProperties = IProperties.Factory.combine (m_propertyOverrides, appProperties);
+        }
+        if ($assert.ENABLED) $assert.ASSERT (toolProperties != null, "toolProperties is null"); // can be empty, though
+
+        final Logger current = Logger.getLogger ();
+        final Logger log = AppLoggers.create (m_appName, toolProperties, current);
+        
+        if (log.atTRACE1 ())
+        {
+            log.trace1 ("run", "complete tool properties:");
+            toolProperties.list (log.getWriter ());
+        }
+        
+        try
+        {
+            Logger.push (log);
+            m_log = log;
+        
+            _run (toolProperties);
+        }
+        finally
+        {
+            if (m_log != null)
+            {
+                Logger.pop (m_log);
+                m_log = null;
+            }
+        }
+    }
+    
+
+    /**
+     * @param path [null is equivalent to empty array]
+     * @param canonical
+     */
+    public synchronized void setCoveragePath (String [] path, final boolean canonical)
+    {
+        if ((path == null) || (path.length == 0))
+            m_coveragePath = IConstants.EMPTY_FILE_ARRAY;
+        else
+            m_coveragePath = Files.pathToFiles (path, canonical);
+        
+        m_canonical = canonical;
+    }
+    
+    public synchronized void setScanCoveragePath (final boolean scan)
+    {
+        m_scanCoveragePath = scan;
+    }
+    
+    /**
+     * @param path [null is equivalent to no source path]
+     */
+    public synchronized void setSourcePath (final String [] path)
+    {
+        if (path == null)
+            m_sourcePath = null;
+        else
+            m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path
+    }
+
+    /**
+     * 
+     * @param specs [null is equivalent to no filtering (everything is included)]
+     */    
+    public synchronized final void setInclExclFilter (final String [] specs)
+    {
+        if (specs == null)
+            m_coverageFilter = null;
+        else
+            m_coverageFilter = IInclExclFilter.Factory.create (specs);
+    }
+    
+    /**
+     * 
+     * @param className [may not be null or empty]
+     * @param args [null is equivalent to an empty array]
+     */
+    public synchronized void setAppClass (final String className, final String [] args)
+    {
+        if ((className == null) || (className.length () == 0))
+            throw new IllegalArgumentException ("null/empty input: className");
+        
+        if (args != null)
+        {
+            final String [] _args = (String []) args.clone ();
+             
+            for (int a = 0; a < _args.length; ++ a)
+                if (_args [a] == null) throw new IllegalArgumentException ("null input: args[" + a + "]");
+                
+            m_appArgs = _args;
+        }
+        else
+        {
+            m_appArgs = IConstants.EMPTY_STRING_ARRAY;
+        }
+        
+        m_appClassName = className;
+    }
+    
+    public synchronized void setDumpSessionData (final boolean dump)
+    {
+        m_dumpSessionData = dump;    
+    }
+    
+    /**
+     * 
+     * @param fileName [null unsets the previous override setting]
+     */
+    public synchronized final void setSessionOutFile (final String fileName)
+    {
+        if (fileName == null)
+            m_sdataOutFile = null;
+        else
+        {
+            final File _file = new File (fileName);
+                
+            if (_file.exists () && ! _file.isFile ())
+                throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]");
+                
+            m_sdataOutFile = _file;
+        }
+    }
+    
+    /**
+     * 
+     * @param merge [null unsets the previous override setting]
+     */
+    public synchronized final void setSessionOutMerge (final Boolean merge)
+    {
+        m_sdataOutMerge = merge;
+    }
+    
+    /**
+     * 
+     * @param types [may not be null]
+     */
+    public synchronized void setReportTypes (final String [] types)
+    {
+        if (types == null) throw new IllegalArgumentException ("null input: types");
+        
+        final String [] reportTypes = Strings.removeDuplicates (types, true);
+        if (reportTypes.length == 0) throw new IllegalArgumentException ("empty input: types");
+        
+        if ($assert.ENABLED) $assert.ASSERT (reportTypes != null && reportTypes.length  > 0);
+        
+        
+        final IReportGenerator [] reportGenerators = new IReportGenerator [reportTypes.length];
+        for (int t = 0; t < reportTypes.length; ++ t)
+        {
+            reportGenerators [t] = AbstractReportGenerator.create (reportTypes [t]);
+        }
+        
+        m_reportGenerators = reportGenerators;
+    }
+
+    // protected: .............................................................
+
+
+    protected void validateState ()
+    {
+        super.validateState ();
+        
+        if ((m_appClassName == null) || (m_appClassName.length () == 0))
+            throw new IllegalStateException ("application class name not set");
+        
+        if (m_appArgs == null)
+            throw new IllegalStateException ("application arguments not set");
+
+        if (m_coveragePath == null)
+            throw new IllegalStateException ("coverage path not set");
+        
+        // [m_coverageFilter can be null]
+        
+        // [m_sdataOutFile can be null]
+        // [m_sdataOutMerge can be null]
+        
+        if ((m_reportGenerators == null) || (m_reportGenerators.length == 0))
+            throw new IllegalStateException ("report types not set");
+
+        // [m_sourcePath can be null/empty]
+        
+        // [m_propertyOverrides can be null]
+    }
+    
+    
+    protected void _run (final IProperties toolProperties)
+    {
+        final Logger log = m_log;
+        
+        final boolean verbose = log.atVERBOSE ();
+        if (verbose)
+        {
+            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
+            
+            // [assertion: m_coveragePath != null]
+            log.verbose ("coverage path:");
+            log.verbose ("{");
+            for (int p = 0; p < m_coveragePath.length; ++ p)
+            {
+                final File f = m_coveragePath [p];
+                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
+                
+                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
+            }
+            log.verbose ("}");
+            
+            if ((m_sourcePath == null) || (m_sourcePath.length == 0))
+            {
+                log.verbose ("source path not set");
+            }
+            else
+            {
+                log.verbose ("source path:");
+                log.verbose ("{");
+                for (int p = 0; p < m_sourcePath.length; ++ p)
+                {
+                    final File f = m_sourcePath [p];
+                    final String nonexistent = f.exists () ? "" : "{nonexistent} ";
+                    
+                    log.verbose ("  " + nonexistent + f.getAbsolutePath ());
+                }
+                log.verbose ("}");
+            }
+        }
+        
+        // get the data out settings [note: this is not conditioned on m_dumpRawData]:
+        File sdataOutFile = m_sdataOutFile;
+        Boolean sdataOutMerge = m_sdataOutMerge;
+        {
+            if (sdataOutFile == null)
+                sdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE,
+                                                                     EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE));
+            
+            if (sdataOutMerge == null)
+            {
+                final String _dataOutMerge = toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_MERGE,
+                                                                         EMMAProperties.DEFAULT_SESSION_DATA_OUT_MERGE.toString ());
+                sdataOutMerge = Property.toBoolean (_dataOutMerge) ? Boolean.TRUE : Boolean.FALSE;
+            } 
+        }
+        
+        if (verbose && m_dumpSessionData)
+        {
+            log.verbose ("session data output file: " + sdataOutFile.getAbsolutePath ());
+            log.verbose ("session data output merge mode: " + sdataOutMerge);
+        }
+        
+        // get instr class loader delegation filter settings:
+        final IInclExclFilter forcedDelegationFilter
+            = IInclExclFilter.Factory.create (toolProperties.getProperty (InstrClassLoader.PROPERTY_FORCED_DELEGATION_FILTER),
+                                              COMMA_DELIMITERS, FORCED_DELEGATION_FILTER_SPECS);
+        final IInclExclFilter throughDelegationFilter
+            = IInclExclFilter.Factory.create (toolProperties.getProperty (InstrClassLoader.PROPERTY_THROUGH_DELEGATION_FILTER),
+                                              COMMA_DELIMITERS, null);
+        
+
+        // TODO: consider injecting Runtime straight into appLoader namespace...
+        // TODO: create a thread group for all exit hooks?
+
+
+        // get a handle to exit hook manager singleton:
+        ExitHookManager runnerExitHookManager = null;
+        try
+        {
+            runnerExitHookManager = ExitHookManager.getSingleton (); // can throw
+        }
+        catch (Exception e)
+        {
+            // TODO: log/handle/warn
+            e.printStackTrace (System.out);
+        }
+
+        AppRunnerExitHook runnerExitHook = null;
+        RuntimeException failure = null;
+        
+        try
+        { 
+            SourcePathCache srcpathCache = null;
+            if (m_sourcePath != null) srcpathCache = new SourcePathCache (m_sourcePath, true); // ignore non-existent source dirs
+            
+            // create session data containers:
+            ICoverageData cdata = RT.getCoverageData ();
+            if ($assert.ENABLED) $assert.ASSERT (cdata != null, "cdata is null");
+            
+            IMetaData mdata = DataFactory.newMetaData (CoverageOptionsFactory.create (toolProperties));
+            
+            runnerExitHook = new AppRunnerExitHook (log, m_dumpSessionData, sdataOutFile, sdataOutMerge.booleanValue (), mdata, cdata, m_reportGenerators, srcpathCache, toolProperties);
+            
+            if (runnerExitHookManager != null)
+                runnerExitHookManager.addExitHook (runnerExitHook);
+            
+            // --------------[ start of exit hook-protected section ]--------------
+            
+            Map classIOCache = null;
+            
+            // scan the classpath to populate the initial metadata:
+            if (m_scanCoveragePath)
+            {
+                if (USE_SOFT_CACHE)
+                    classIOCache = new SoftValueMap (INIT_CACHE_CAPACITY, 0.75F, SOFT_CACHE_READ_CHK_FREQUENCY, SOFT_CACHE_WRITE_CHK_FREQUENCY);
+                else
+                    classIOCache = new HashMap (INIT_CACHE_CAPACITY, 0.75F);
+                    
+                final ClassPathProcessorST processor = new ClassPathProcessorST (m_coveragePath, m_canonical, mdata, m_coverageFilter, classIOCache);
+                
+                // with a bit of work [ClassPathProcessorST needs to lock on the
+                // metadata, etc] this could be run concurrently with the app
+                // itself to improve perceived performance, however, I am not
+                // going to invest time in this; 
+                
+                // populate 'cache' [optional] and 'mdata':
+                processor.run ();
+                
+                if (log.atTRACE1 ())
+                {
+                    log.trace1 ("run", "class cache size after cp scan: " + classIOCache.size ());
+                    log.trace1 ("run", "metadata size after cp scan: " + mdata.size ());
+                }
+            }
+            
+            
+            // app runner does not need these handles anymore [only the exit hook runner maintains them]:
+            srcpathCache = null;
+            cdata = null;
+            
+            final ClassLoader appLoader;
+            {
+                final IClassLoadHook loadHook = new InstrClassLoadHook (m_coverageFilter, mdata);
+                 
+                try
+                {
+                    appLoader = new InstrClassLoader (m_delegate, m_coveragePath, forcedDelegationFilter, throughDelegationFilter, loadHook, classIOCache);
+                }
+                catch (SecurityException se)
+                {
+                    throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
+                }
+                catch (MalformedURLException mue)
+                {
+                    throw new EMMARuntimeException (mue); 
+                }
+            }
+            
+            // app runner does not need these handles anymore:
+            mdata = null;
+            classIOCache = null;
+
+            
+            final ClassLoader contextLoader;
+            boolean contextLoaderSet = false;
+            if (SET_CURRENT_CONTEXT_LOADER)
+            {
+                try
+                {
+                    final Thread currentThread = Thread.currentThread (); 
+                    
+                    // TODO: rethink if this is the right place to do this
+                    contextLoader = currentThread.getContextClassLoader ();
+                    currentThread.setContextClassLoader (appLoader);
+                    
+                    contextLoaderSet = true;
+                }
+                catch (SecurityException se)
+                {
+                    throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
+                }
+            }
+            
+            
+            ThreadGroup appThreadGroup = null;
+            try
+            {
+                // load [and possibly initialize] the app class:
+                
+                final Class appClass;
+                try
+                {
+                    // load [and force early initialization if INIT_AT_LOAD_TIME is 'true']:
+                    appClass = Class.forName (m_appClassName, INIT_AT_LOAD_TIME, appLoader);
+                }
+                catch (ClassNotFoundException cnfe)
+                {
+                    // TODO: dump the classloader tree into the error message as well
+                    throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String [] {m_appClassName}, cnfe);
+                }
+                catch (ExceptionInInitializerError eiie) // this should not happen for INIT_AT_LOAD_TIME=false
+                {
+                    final Throwable cause = eiie.getException ();
+                    
+                    throw new EMMARuntimeException (MAIN_CLASS_LOAD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause);
+                }
+                catch (Throwable t)
+                {
+                    throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String [] {m_appClassName}, t);
+                }
+                
+                // ensure that the app is bootstrapped using appLoader:
+                {
+                    final ClassLoader actualLoader = appClass.getClassLoader ();
+                    if (actualLoader != appLoader)
+                    {
+                        final String loaderName = actualLoader != null ?  actualLoader.getClass ().getName () : "<PRIMORDIAL>";
+                        
+                        throw new EMMARuntimeException (MAIN_CLASS_BAD_DELEGATION, new String [] {IAppConstants.APP_NAME, m_appClassName, loaderName});
+                    }
+                }
+    
+                // run the app's main():
+                
+                final Method appMain;
+                try
+                {
+                    // this causes initialization on some non-Sun-compatible JVMs [ignore]:
+                    appMain = appClass.getMethod ("main", MAIN_TYPE); // Sun JVMs do not seem to require the method to be declared
+                }
+                catch (Throwable t)
+                {
+                    throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String [] {m_appClassName}, t);
+                }            
+                
+                Invoker invoker = new Invoker (appMain, null, new Object [] {m_appArgs});
+                
+                appThreadGroup = new ThreadGroup (IAppConstants.APP_NAME + " thread group [" + m_appClassName + "]");
+                appThreadGroup.setDaemon (true);
+                
+                Thread appThread = new Thread (appThreadGroup, invoker, IAppConstants.APP_NAME + " main() thread");
+                appThread.setContextClassLoader (appLoader);
+                
+                // --- [app start] ----
+                
+                appThread.start ();
+                
+                try {appThread.join (); } catch (InterruptedException ignore) {}
+                appThread = null;
+                
+                joinNonDeamonThreads (appThreadGroup);
+                
+                // --- [app end] ----
+                
+                if (log.atTRACE1 ())
+                {
+                    if (appLoader instanceof InstrClassLoader) ((InstrClassLoader) appLoader).debugDump (log.getWriter ());
+                }
+                
+                final Throwable mainFailure = invoker.getFailure ();
+                invoker = null;
+                
+                if (mainFailure != null)
+                {
+                    if (mainFailure instanceof InvocationTargetException)
+                    {
+                        final Throwable cause = ((InvocationTargetException) mainFailure).getTargetException ();
+                        
+                        throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause);
+                    }
+                    else if (mainFailure instanceof ExceptionInInitializerError)
+                    {
+                        // this catch block is never entered if INIT_AT_LOAD_TIME is 'true'
+                        final Throwable cause = ((ExceptionInInitializerError) mainFailure).getException ();
+                        
+                        throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause);
+                    }
+                    else if ((mainFailure instanceof IllegalAccessException)   ||
+                             (mainFailure instanceof IllegalArgumentException) ||
+                             (mainFailure instanceof NullPointerException))
+                    {
+                        throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String [] {m_appClassName}, mainFailure);
+                    }
+                    else
+                    {
+                        throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, mainFailure.toString ()}, mainFailure);
+                    }
+                }                
+            }
+            catch (SecurityException se)
+            {
+                throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
+            }
+            finally
+            {
+                if (SET_CURRENT_CONTEXT_LOADER && contextLoaderSet)
+                {
+                    try
+                    {
+                        Thread.currentThread ().setContextClassLoader (contextLoader);
+                    }
+                    catch (Throwable ignore) {} 
+                }
+
+                if ((appThreadGroup != null) && ! appThreadGroup.isDestroyed ())
+                try
+                {
+                    appThreadGroup.destroy ();
+                    appThreadGroup = null;
+                }
+                catch (Throwable ignore) {}                
+            }
+        }
+        catch (RuntimeException re)
+        {
+            failure = re; // should be EMMARuntimeException only if there are no errors above
+        }
+        finally
+        {
+            RT.reset (false, false);
+        }
+        
+        if ($assert.ENABLED) $assert.ASSERT (runnerExitHook != null, "reportExitHook = null");
+        runnerExitHook.run (); // that this may be a noop (if the shutdown sequence got there first)
+        
+        // [assertion: the report exit hook is done]
+                
+        if (runnerExitHookManager != null)
+        {
+            runnerExitHookManager.removeExitHook (runnerExitHook); // Ok if this fails
+            runnerExitHookManager = null;
+        }
+        
+        // ---------------[ end of exit hook-protected section ]---------------
+        
+        
+        final Throwable exitHookDataDumpFailure = runnerExitHook.getDataDumpFailure ();
+        final List /* Throwable */ exitHookReportFailures = runnerExitHook.getReportFailures ();
+        runnerExitHook = null;        
+        
+        if (failure != null) // 'failure' takes precedence over any possible exit hook's problems
+        {
+            throw wrapFailure (failure);
+        }
+        else if ((exitHookDataDumpFailure != null) || (exitHookReportFailures != null))
+        {
+            if (exitHookDataDumpFailure != null)
+                log.log (Logger.SEVERE, "exception while persisting raw session data:", exitHookDataDumpFailure);
+            
+            Throwable firstReportFailure = null;
+            if (exitHookReportFailures != null)
+            {
+                for (Iterator i = exitHookReportFailures.iterator (); i.hasNext (); )
+                {
+                    final Throwable reportFailure = (Throwable) i.next ();                
+                    if (firstReportFailure == null) firstReportFailure = reportFailure;
+                    
+                    log.log (Logger.SEVERE, "exception while creating a report:", reportFailure);
+                }
+            }
+            
+            if (exitHookDataDumpFailure != null)
+                throw wrapFailure (exitHookDataDumpFailure);
+            else if (firstReportFailure != null) // redundant check
+                throw wrapFailure (firstReportFailure);
+        }
+
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class Invoker implements Runnable
+    {
+        Invoker (final Method method, final Object target, final Object [] args)
+        {
+            if (method == null) throw new IllegalArgumentException ("null input: method");
+            if (args == null) throw new IllegalArgumentException ("null input: args");
+            
+            m_method = method;
+            m_target = target;
+            m_args = args;
+        }
+        
+        public void run ()
+        {
+            try
+            {
+                m_method.invoke (m_target, m_args);
+            }
+            catch (Throwable t)
+            {
+                m_failure = t;
+            }
+        }
+        
+        Throwable getFailure ()
+        {
+            return m_failure;
+        }
+        
+        
+        private final Method m_method;
+        private final Object m_target;
+        private final Object [] m_args;
+        private Throwable m_failure;
+        
+    } // end of nested class
+    
+    
+    private static final class AppRunnerExitHook implements Runnable
+    {
+        public synchronized void run ()
+        {
+            try
+            {
+                if (! m_done)
+                {
+                    // grab data snapshots:
+                    
+                    final IMetaData mdataSnashot = m_mdata.shallowCopy ();
+                    m_mdata = null;
+                    final ICoverageData cdataSnapshot = m_cdata.shallowCopy ();
+                    m_cdata = null;
+                    
+                    if (mdataSnashot.isEmpty ())
+                    {
+                        m_log.warning ("no metadata collected at runtime [no reports generated]");
+                        
+                        return;
+                    }
+                    
+                    if (cdataSnapshot.isEmpty ())
+                    {
+                        m_log.warning ("no coverage data collected at runtime [all reports will be empty]");
+                    }
+                    
+                    final ISessionData sdata = new SessionData (mdataSnashot, cdataSnapshot);
+
+                    // if requested, dump raw data before running report generators:                    
+                    // [note that the raw dumps and reports will be consistent wrt
+                    // the session data they represent]
+                    
+                    if (m_dumpRawData && (m_sdataOutFile != null))
+                    {
+                       try
+                        {
+                            final boolean info = m_log.atINFO ();
+                            
+                            final long start = info ? System.currentTimeMillis () : 0;
+                            {
+                                DataFactory.persist (sdata, m_sdataOutFile, m_sdataOutMerge);
+                            }
+                            if (info)
+                            {
+                                final long end = System.currentTimeMillis ();
+                                
+                                m_log.info ("raw session data " + (m_sdataOutMerge ? "merged into" : "written to") + " [" + m_sdataOutFile.getAbsolutePath () + "] {in " + (end - start) + " ms}");
+                            }
+                        }
+                        catch (Throwable t)
+                        {
+                            m_dataDumpFailure = t;
+                        }
+                    }
+                    
+                    for (int g = 0; g < m_generators.length; ++ g)
+                    {
+                        final IReportGenerator generator = m_generators [g];
+                        
+                        if (generator != null)
+                        {
+                            try
+                            {
+                                generator.process (mdataSnashot, cdataSnapshot, m_cache, m_properties);
+                            }
+                            catch (Throwable t)
+                            {
+                                if (m_reportFailures == null) m_reportFailures = new ArrayList ();
+                                m_reportFailures.add (t);
+                                
+                                continue;
+                            }
+                            finally
+                            {
+                                try { generator.cleanup (); } catch (Throwable ignore) {}
+                                m_generators [g] = null;
+                            }
+                        }
+                    }                    
+                }
+            }
+            finally
+            {
+                m_generators = null;
+                m_mdata = null;
+                m_cdata = null;
+                m_properties = null;
+                m_cache = null;
+                
+                m_done = true;
+            }
+        }
+        
+        // note: because ExitHookManager is a lazily created static singleton the
+        // correct thing to do is to pass an explicit Logger into each exit hook runner
+        // instead of relying on thread inheritance:
+        
+        AppRunnerExitHook (final Logger log,
+                           final boolean dumpRawData, final File sdataOutFile, final boolean sdataOutMerge,
+                           final IMetaData mdata, final ICoverageData cdata,
+                           final IReportGenerator [] generators,
+                           final SourcePathCache cache, final IProperties properties)
+        {
+            if (log == null) throw new IllegalArgumentException ("null input: log");
+            if ((generators == null) || (generators.length == 0)) throw new IllegalArgumentException ("null/empty input: generators");
+            if (mdata == null) throw new IllegalArgumentException ("null input: mdata");
+            if (cdata == null) throw new IllegalArgumentException ("null input: cdata");
+            if (properties == null) throw new IllegalArgumentException ("null input: properties");
+            
+            m_log = log;
+            
+            m_dumpRawData = dumpRawData;
+            m_sdataOutFile = sdataOutFile;
+            m_sdataOutMerge = sdataOutMerge;
+            
+            m_generators = (IReportGenerator []) generators.clone ();
+            m_mdata = mdata;
+            m_cdata = cdata;
+            m_cache = cache;
+            m_properties = properties;
+        }
+
+        
+        synchronized Throwable getDataDumpFailure ()
+        {
+            return m_dataDumpFailure;
+        }
+        
+        synchronized List /* Throwable */ getReportFailures ()
+        {
+            return m_reportFailures;
+        }
+
+
+        private final Logger m_log;
+        private final boolean m_dumpRawData;
+        private final File m_sdataOutFile;
+        private final boolean m_sdataOutMerge;
+        
+        private IReportGenerator [] m_generators;
+        private IMetaData m_mdata;
+        private ICoverageData m_cdata;
+        private SourcePathCache m_cache;
+        private IProperties m_properties;
+        private boolean m_done;
+        private Throwable m_dataDumpFailure;
+        private List /* Throwable */ m_reportFailures;
+        
+    } // end of nested class 
+    
+    
+    private AppRunner (final ClassLoader delegate)
+    {
+        m_delegate = delegate;
+        m_coveragePath = IConstants.EMPTY_FILE_ARRAY;
+    }
+    
+    
+    private static void joinNonDeamonThreads (final ThreadGroup group)
+    {
+        if (group == null) throw new IllegalArgumentException ("null input: group");
+        
+        final List threads = new ArrayList ();
+        while (true)
+        {
+            threads.clear ();
+            
+            // note: group.activeCount() is only an estimate as more threads
+            // could get created while we are doing this [if 'aliveThreads'
+            // array is too short, the extra threads are silently ignored]:
+            
+            Thread [] aliveThreads;
+            final int aliveCount;
+            
+            // enumerate [recursively] all threads in 'group':
+            synchronized (group)
+            {
+                aliveThreads = new Thread [group.activeCount () << 1];
+                aliveCount = group.enumerate (aliveThreads, true);
+            }
+            
+            for (int t = 0; t < aliveCount; t++)
+            {
+                if (! aliveThreads [t].isDaemon ())
+                    threads.add (aliveThreads [t]);
+            }            
+            aliveThreads = null;
+            
+            if (threads.isEmpty ())
+                break; // note: this logic does not work if daemon threads are spawning non-daemon ones
+            else
+            {
+                for (Iterator i = threads.iterator (); i.hasNext (); )
+                {
+                    try
+                    {
+                        ((Thread) i.next ()).join (); 
+                    }
+                    catch (InterruptedException ignore) {}
+                }
+            }
+        }
+    }
+    
+    private static RuntimeException wrapFailure (final Throwable t)
+    {
+        if (Exceptions.unexpectedFailure (t, EXPECTED_FAILURES))
+            return new EMMARuntimeException (UNEXPECTED_FAILURE,
+                                            new Object [] {t.toString (), IAppConstants.APP_BUG_REPORT_LINK},
+                                            t);
+        else if (t instanceof RuntimeException)
+            return (RuntimeException) t;
+        else
+            return new EMMARuntimeException (t);
+    }
+    
+
+    // caller-settable state [scoped to this runner instance]:
+    
+    private final ClassLoader m_delegate;
+    
+    private String m_appClassName;      // required to be non-null for run()
+    private String [] m_appArgs;        // required to be non-null for run()
+    
+    private File [] m_coveragePath;     // required to be non-null/non-empty for run()
+    private boolean m_canonical;
+    private boolean m_scanCoveragePath;
+    private IInclExclFilter m_coverageFilter; // can be null for run()   
+    
+    private boolean m_dumpSessionData;
+    private File m_sdataOutFile; // user override; can be null for run()
+    private Boolean m_sdataOutMerge; // user override; can be null for run()
+   
+    private IReportGenerator [] m_reportGenerators; // required to be non-null for run()
+    private File [] m_sourcePath;                   // can be null/empty for run()
+    
+    // it is attractive to detect errors at load time, but this may allow
+    // threads created by <clinit> code to escape; on the other hand, classes
+    // that do not override main() will not get initialized this way and will
+    // not register with our runtime [which seems a minor problem at this point]: 
+    private static final boolean INIT_AT_LOAD_TIME = false;
+    
+    // setting the context loader on AppRunner's thread should not
+    // be necessary since the app is run in a dedicated thread group;
+    // however, if INIT_AT_LOAD_TIME=true the app's <clinit> code
+    // should run with an adjusted context loader:
+    private static final boolean SET_CURRENT_CONTEXT_LOADER = INIT_AT_LOAD_TIME;
+    
+    // a soft cache is ideal for managing class definitions that are read during
+    // the initial classpath scan; however, the default LRU policy parameters for
+    // clearing SoftReferences in Sun's J2SDK 1.3+ render them next to useless
+    // in the client HotSpot JVM (in which this tool will probably run most often);
+    // using a hard cache guarantees 100% cache hit rate but can also raise the
+    // memory requirements significantly beyond the needs of the original app.
+    // [see bug refs 4471453, 4806720, 4888056, 4239645]
+    //
+    // resolution for now: use a soft cache anyway and doc that to make it useful
+    // for non-trivial apps the user should use -Xms or -XX:SoftRefLRUPolicyMSPerMB
+    // JVM options or use a server HotSpot JVM
+    private static final boolean USE_SOFT_CACHE = true;
+    
+    private static final int INIT_CACHE_CAPACITY = 2003; // prime
+    private static final int SOFT_CACHE_READ_CHK_FREQUENCY = 100;
+    private static final int SOFT_CACHE_WRITE_CHK_FREQUENCY = 100;
+
+    private static final String [] FORCED_DELEGATION_FILTER_SPECS; // set in <clinit>
+    private static final Class [] MAIN_TYPE = new Class [] {String [].class};
+    
+    private static final Class [] EXPECTED_FAILURES; // set in <clinit>
+    
+    protected static final String COMMA_DELIMITERS    = "," + Strings.WHITE_SPACE;
+    protected static final String PATH_DELIMITERS     = ",".concat (File.pathSeparator);
+    
+    static
+    {
+        EXPECTED_FAILURES = new Class []
+        {
+            EMMARuntimeException.class,
+            IllegalArgumentException.class,
+            IllegalStateException.class,
+        };
+                
+        FORCED_DELEGATION_FILTER_SPECS = new String [] {"+" + IAppConstants.APP_PACKAGE + ".*"};
+    }
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/ClassPathCacheEntry.java b/core/java12/com/vladium/emma/rt/ClassPathCacheEntry.java
new file mode 100644
index 0000000..a8361b4
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/ClassPathCacheEntry.java
@@ -0,0 +1,47 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassPathCacheEntry.java,v 1.1.1.1 2004/05/09 16:57:43 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ClassPathCacheEntry
+{
+    // public: ................................................................
+    
+    // getters not provided [the fields are final]
+    
+    public final byte [] m_bytes;
+    public final String m_srcURL; // a String is more compact that java.net.URL
+    
+    
+    public ClassPathCacheEntry (final byte [] bytes, final String srcURL)
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (bytes != null, "bytes = null");
+            $assert.ASSERT (srcURL != null, "srcURL = null");
+        }
+        
+        m_bytes = bytes;
+        m_srcURL = srcURL;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/ClassPathProcessorST.java b/core/java12/com/vladium/emma/rt/ClassPathProcessorST.java
new file mode 100644
index 0000000..2e06948
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/ClassPathProcessorST.java
@@ -0,0 +1,397 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassPathProcessorST.java,v 1.1.1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.parser.ClassDefParser;
+import com.vladium.logging.Logger;
+import com.vladium.util.ByteArrayOStream;
+import com.vladium.util.Files;
+import com.vladium.util.IPathEnumerator;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppErrorCodes;
+import com.vladium.emma.EMMARuntimeException;
+import com.vladium.emma.data.IMetaData;
+import com.vladium.emma.filter.IInclExclFilter;
+import com.vladium.emma.instr.InstrVisitor;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class ClassPathProcessorST implements IPathEnumerator.IPathHandler, IAppErrorCodes
+{
+    // public: ................................................................
+    
+    public void run ()
+    {
+        long start = System.currentTimeMillis ();
+        
+        // construct instr path enumerator [throws on illegal input only]:
+        final IPathEnumerator enumerator = IPathEnumerator.Factory.create (m_path, m_canonical, this);
+        
+        // allocate I/O buffers:
+        m_readbuf = new byte [BUF_SIZE]; // don't reuse this across run() calls to reset it to the original size
+        m_readpos = 0;
+        m_baos = new ByteArrayOStream (BUF_SIZE); // don't reuse this across run() calls to reset it to the original size
+        
+        if (m_log.atINFO ())
+        {
+            m_log.info ("processing classpath ...");
+        }
+        
+        // actual work is driven by the path enumerator:
+        try
+        {
+            enumerator.enumerate ();
+        }
+        catch (IOException ioe)
+        {
+            throw new EMMARuntimeException (INSTR_IO_FAILURE, ioe);
+        }
+        
+        if (m_log.atINFO ())
+        {
+            final long end = System.currentTimeMillis ();
+            
+            m_log.info ("[" + m_classCount + " class(es) processed in " + (end - start) + " ms]");
+        }
+    }
+    
+    // IPathEnumerator.IPathHandler:
+
+    public void handleArchiveStart (final File parentDir, final File archive, final Manifest manifest)
+    {
+        m_archiveFile = Files.newFile (parentDir, archive.getPath ());
+    }
+
+    public void handleArchiveEntry (final JarInputStream in, final ZipEntry entry)
+    {
+        if (m_log.atTRACE2 ()) m_log.trace2 ("handleArchiveEntry", "[" + entry.getName () + "]");
+        
+        final String name = entry.getName ();
+        final String lcName = name.toLowerCase ();
+        
+        if (lcName.endsWith (".class"))
+        {
+            final String className = name.substring (0, name.length () - 6).replace ('/', '.');
+            
+            if ((m_coverageFilter == null) || m_coverageFilter.included (className))
+            {
+                String srcURL = null;
+                InputStream clsin = null;
+                try
+                {
+                    readZipEntry (in, entry);
+                    
+                    srcURL = "jar:".concat (m_archiveFile.toURL ().toExternalForm ()).concat ("!/").concat (name);
+                }
+                catch (FileNotFoundException fnfe)
+                {
+                    // ignore: this should never happen
+                    if ($assert.ENABLED)
+                    {
+                        fnfe.printStackTrace (System.out);
+                    }
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+                finally
+                {
+                    if (clsin != null)
+                        try
+                        {
+                            clsin.close ();
+                            clsin = null;
+                        }
+                        catch (Exception e)
+                        {
+                            // TODO: error code
+                            throw new EMMARuntimeException (e);
+                        }
+                }
+                
+                // [original class def read into m_readbuf]
+                
+                try
+                {
+                    ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos);
+                    if (! clsDef.isInterface ()) ++ m_classCount;
+                    
+                    m_visitor.process (clsDef, false, false, true, m_instrResult); // get metadata only
+                    clsDef = null;
+                    
+                    boolean cacheClassDef = true;
+                  
+                    if (m_instrResult.m_descriptor != null)
+                    {
+                        // do not overwrite existing descriptors to support "first
+                        // in the classpath wins" semantics:
+                        
+                        if (! m_mdata.add (m_instrResult.m_descriptor, false))
+                           cacheClassDef = false; 
+                    }
+                    
+                    if (cacheClassDef && (m_cache != null))
+                    {
+                        final byte [] bytes = new byte [m_readpos];
+                        System.arraycopy (m_readbuf, 0, bytes, 0, m_readpos);
+                        
+                        m_cache.put (className, new ClassPathCacheEntry (bytes, srcURL));
+                    }
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+            }
+        }
+    }
+
+    public void handleArchiveEnd (final File parentDir, final File archive)
+    {
+        m_archiveFile = null;
+    }
+
+
+    public void handleDirStart (final File pathDir, final File dir)
+    {
+        // do nothing
+    }
+
+    public void handleFile (final File pathDir, final File file)
+    {
+        if (m_log.atTRACE2 ()) m_log.trace2 ("handleFile", "[" + pathDir + "] [" + file + "]");
+        
+        final String name = file.getPath ();
+        final String lcName = name.toLowerCase ();
+        
+        if (lcName.endsWith (".class"))
+        {
+            final String className = name.substring (0, name.length () - 6).replace (File.separatorChar, '.');
+            
+            if ((m_coverageFilter == null) || m_coverageFilter.included (className))
+            {
+                String srcURL = null;
+                InputStream clsin = null;
+                try
+                {
+                    final File inFile = Files.newFile (pathDir, file.getPath ());
+                    readFile (inFile);
+                    
+                    srcURL = inFile.toURL ().toExternalForm ();
+                }
+                catch (FileNotFoundException fnfe)
+                {
+                    // ignore: this should never happen
+                    if ($assert.ENABLED)
+                    {
+                        fnfe.printStackTrace (System.out);
+                    }
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+                finally
+                {
+                    if (clsin != null)
+                        try
+                        {
+                            clsin.close ();
+                            clsin = null;
+                        }
+                        catch (Exception e)
+                        {
+                            // TODO: error code
+                            throw new EMMARuntimeException (e);
+                        }
+                }
+                
+                // [original class def read into m_readbuf]
+                
+                try
+                {
+                    ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos);
+                    if (! clsDef.isInterface ()) ++ m_classCount;
+                    
+                    m_visitor.process (clsDef, false, false, true, m_instrResult); // get metadata only
+                    clsDef = null;
+                    
+                    
+                    boolean cacheClassDef = true;
+                  
+                    if (m_instrResult.m_descriptor != null)
+                    {
+                        // do not overwrite existing descriptors to support "first
+                        // in the classpath wins" semantics:
+                        
+                        if (! m_mdata.add (m_instrResult.m_descriptor, false))
+                           cacheClassDef = false; 
+                    }
+                    
+                    if (cacheClassDef && (m_cache != null))
+                    {
+                        final byte [] bytes = new byte [m_readpos];
+                        System.arraycopy (m_readbuf, 0, bytes, 0, m_readpos);
+                        
+                        m_cache.put (className, new ClassPathCacheEntry (bytes, srcURL));
+                    }
+                }
+                catch (IOException ioe)
+                {
+                    // TODO: error code
+                    throw new EMMARuntimeException (ioe);
+                }
+            }
+        }
+    }
+
+    public void handleDirEnd (final File pathDir, final File dir)
+    {
+        // do nothing
+    }
+
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    /*
+     * null 'cache' indicates to only populate the metadata and not bother with
+     * caching instrumented class defs
+     */
+    ClassPathProcessorST (final File [] path, final boolean canonical,
+                          final IMetaData mdata, final IInclExclFilter filter,
+                          final Map cache)
+    {
+        if (path == null) throw new IllegalArgumentException ("null input: path");
+        if (mdata == null) throw new IllegalArgumentException ("null input: mdata");
+        
+        m_path = path;
+        m_canonical = canonical;
+        m_mdata = mdata;
+        m_coverageFilter = filter;
+        m_cache = cache; // can be null
+        m_visitor = new InstrVisitor (mdata.getOptions ());
+        m_instrResult = new InstrVisitor.InstrResult ();
+        
+        m_log = Logger.getLogger ();
+    }
+
+    // private: ...............................................................
+
+
+    /*
+     * Reads into m_readbuf (m_readpos is updated correspondingly)
+     */
+    private void readFile (final File file)
+        throws IOException
+    {
+        final int length = (int) file.length ();
+        
+        ensureReadCapacity (length);
+        
+        InputStream in = null;
+        try
+        {
+            in = new FileInputStream (file);
+            
+            int totalread = 0;
+            for (int read;
+                 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0;
+                 totalread += read);
+            m_readpos = totalread;
+        } 
+        finally
+        {
+            if (in != null) try { in.close (); } catch (Exception ignore) {} 
+        }
+    }
+    
+    /*
+     * Reads into m_readbuf (m_readpos is updated correspondingly)
+     */
+    private void readZipEntry (final ZipInputStream in, final ZipEntry entry)
+        throws IOException
+    {
+        final int length = (int) entry.getSize (); // can be -1 if unknown
+        
+        if (length >= 0)
+        {
+            ensureReadCapacity (length);
+            
+            int totalread = 0;
+            for (int read;
+                 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0;
+                 totalread += read);
+            m_readpos = totalread;
+        }
+        else
+        {
+            ensureReadCapacity (BUF_SIZE);
+            
+            m_baos.reset ();
+            for (int read; (read = in.read (m_readbuf)) >= 0; m_baos.write (m_readbuf, 0, read));
+            
+            m_readbuf = m_baos.copyByteArray ();
+            m_readpos = m_readbuf.length;
+        }
+    }   
+ 
+    private void ensureReadCapacity (final int capacity)
+    {
+        if (m_readbuf.length < capacity)
+        {
+            final int readbuflen = m_readbuf.length;
+            m_readbuf = null;
+            m_readbuf = new byte [Math.max (readbuflen << 1, capacity)];
+        }
+    }
+
+
+    private final File [] m_path; // never null
+    private final boolean m_canonical;
+    private final IMetaData m_mdata; // never null
+    private final IInclExclFilter m_coverageFilter; // can be null
+    private final InstrVisitor m_visitor;
+    private final InstrVisitor.InstrResult m_instrResult;
+    private final Map /* classJavaName:String -> ClassPathCacheEntry */ m_cache; // can be null
+    
+    private final Logger m_log; // this class is instantiated and used on a single thread
+    
+    private int m_classCount;
+    
+    private byte [] m_readbuf;
+    private int m_readpos;
+    private ByteArrayOStream m_baos; // TODO: code to guard this from becoming too large
+    
+    private File m_archiveFile;
+    
+    private static final int BUF_SIZE = 32 * 1024;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/IClassLoadHook.java b/core/java12/com/vladium/emma/rt/IClassLoadHook.java
new file mode 100644
index 0000000..57c01e5
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/IClassLoadHook.java
@@ -0,0 +1,73 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IClassLoadHook.java,v 1.1.1.1 2004/05/09 16:57:44 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.IOException;
+
+import com.vladium.util.ByteArrayOStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, 2003
+ */
+public
+interface IClassLoadHook
+{
+    // public: ................................................................
+
+//    /**
+//     * The hook returns 'true' or 'false' based on 'className' only. If it
+//     * returns false, the current loader will load the class bytes itself and
+//     * pass them to ClassLoader.defineClass() unchanged. This is an optimization
+//     * to let the JVM native code do the parsing of class definitions for classes
+//     * that do not match coverage filters instead of doing this in bytecode.  
+//     */
+//    boolean interested (String className);
+    
+    // TODO: figure out a way to avoid all this excessive copying of byte arrays
+    
+    /* TODO: finish
+     * 
+     * The hook reads in the original class definition from 'in' and [possibly]
+     * instruments it, returning the modified class definion [which should
+     * correspond to the original class name]. Only class definitions with names
+     * that were successfully filtered via a previous (although not necessarily
+     * <em>immediately</em> so) call to {@link #interested} will be passed to
+     * this method.<P>
+     * 
+     * It is possible that the hook will determine that it is not interested in
+     * intrumenting the pending class definition [or is unable to do so] only
+     * after reading some content from 'in'. An example would be when the class
+     * definition turns out to be for an interface and the hook does not process
+     * interfaces. Because 'in' can then be left in an uncertain state, the hook
+     * must follow these rules for the two possible outcomes:
+     * <ol>
+     *  <li> if the hook can successfully recover the unmodified class definion
+     * [perhaps because it cloned the original definition or never modified it]:
+     * it should write it into 'out' and return 'true';
+     *  <li> if the hook has lost the original class definion: it should return 'false'.
+     * Following that, the current loader will close() and discard 'in' and load
+     * the class from another, equivalent in content, data stream instance. 
+     * </ol>
+     * 
+     * In any case 'in' and 'out' remain owned [and will be close()d] by the caller.
+     * 
+     * NOTE: the hook should only write to 'out' after reading the entire
+     * class definition in 'bytes' ('out' could be backed by the same array as
+     * 'bytes')
+     * 
+     * @param out [buffered by the caller]
+     */
+    boolean processClassDef (String className,
+                             byte [] bytes, int length,
+                             ByteArrayOStream out)
+        throws IOException;
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/InstrClassLoadHook.java b/core/java12/com/vladium/emma/rt/InstrClassLoadHook.java
new file mode 100644
index 0000000..ff51cc4
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/InstrClassLoadHook.java
@@ -0,0 +1,136 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InstrClassLoadHook.java,v 1.1.1.1 2004/05/09 16:57:44 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.compiler.ClassWriter;
+import com.vladium.jcd.parser.ClassDefParser;
+import com.vladium.util.ByteArrayOStream;
+import com.vladium.util.Descriptors;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.filter.IInclExclFilter;
+import com.vladium.emma.instr.InstrVisitor;
+import com.vladium.emma.data.CoverageOptions;
+import com.vladium.emma.data.IMetaData;
+
+// ----------------------------------------------------------------------------
+/**
+ * MT-safety ensured by the containing loader
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class InstrClassLoadHook implements IClassLoadHook
+{
+    // public: ................................................................
+    
+    /**
+     * @param filter [can be null]
+     */
+    public InstrClassLoadHook (final IInclExclFilter filter, final IMetaData mdata)
+    {
+        if (mdata == null) throw new IllegalArgumentException ("null input: mdata");
+        
+        m_filter = filter; // can be null 
+        m_metadata = mdata;
+        
+        // important to use the same options as the metadata may have been populated earlier:
+        final CoverageOptions options = mdata.getOptions ();
+        m_classDefProcessor = new InstrVisitor (options);
+        
+        m_instrResult = new InstrVisitor.InstrResult ();
+    }
+    
+        
+    public boolean processClassDef (final String className,
+                                    final byte [] bytes, final int length,
+                                    ByteArrayOStream out)
+        throws IOException
+    {
+        if ($assert.ENABLED)
+        {
+            $assert.ASSERT (className != null, "className is null");
+            $assert.ASSERT (bytes != null, "bytes is null");
+            $assert.ASSERT (bytes != null, "out is null");
+        }
+        
+        final IInclExclFilter filter = m_filter;
+        if ((filter == null) || filter.included (className))
+        {
+            final ClassDef clsDef = ClassDefParser.parseClass (bytes, length);
+            final String classVMName = Descriptors.javaNameToVMName (className);
+            
+            final Object lock = m_metadata.lock ();
+            
+            final boolean metadataExists;
+            synchronized (lock)
+            {
+                metadataExists = m_metadata.hasDescriptor (classVMName);
+            }
+            
+            // since this is the first [and only] time the parent loader is
+            // loading the class in question, if metadata for 'className' exists
+            // it means it was created during the app runner's classpath scan --
+            // do not overwrite it (the class def should be the same)
+            
+            // [this picture breaks down if the design changes so that the same
+            // metadata instance could be associated with more than one app loader]
+            
+            m_classDefProcessor.process (clsDef, false, true, ! metadataExists, m_instrResult);
+            
+            boolean useOurs = m_instrResult.m_instrumented;
+            
+            if (m_instrResult.m_descriptor != null) // null means either the metadata existed already or the class is an interface
+            {
+                // try to update metadata [this supports the "no initial full cp
+                // scan mode" in the app runner and also ensures that we pick up
+                // any dynamically generated classes to support (hacky) apps that
+                // do dynamic source generation/compilation]:
+                
+                synchronized (lock)
+                {
+                    // do not force overwrites of existing descriptors to support
+                    // correct handling of race conditions: if another thread
+                    // updates the metadata first, discard our version of the class def
+                    
+                    // [actually, this guard is redundant here because
+                    // right now the hook can only have a single classloader parent
+                    // and the parent's loadClass() is a critical section]
+                    
+                    if (! m_metadata.add (m_instrResult.m_descriptor, false))
+                         useOurs = false; 
+                }
+            }
+            
+            if (useOurs)
+            {                        
+                ClassWriter.writeClassTable (clsDef, out);
+                return true;
+            }
+        }
+
+        return false;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final IInclExclFilter m_filter; // can be null [equivalent to no filtering]
+    private final IMetaData m_metadata; // never null
+    private final InstrVisitor m_classDefProcessor; // never null
+    private final InstrVisitor.InstrResult m_instrResult;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/InstrClassLoader.java b/core/java12/com/vladium/emma/rt/InstrClassLoader.java
new file mode 100644
index 0000000..b8c4277
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/InstrClassLoader.java
@@ -0,0 +1,462 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InstrClassLoader.java,v 1.1.1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.Map;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.ByteArrayOStream;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.filter.IInclExclFilter;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class InstrClassLoader extends URLClassLoader
+{
+    // public: ................................................................
+        
+    // TODO: proper security [use PrivilegedAction as needed]
+    // TODO: [see above as well] need to keep track of res URLs to support [path] exclusion patterns
+    // TODO: improve error handling so it is clear when errors come from buggy instrumentation
+    
+    
+    public static final String PROPERTY_FORCED_DELEGATION_FILTER  = "clsload.forced_delegation_filter";
+    public static final String PROPERTY_THROUGH_DELEGATION_FILTER  = "clsload.through_delegation_filter";
+        
+
+    public InstrClassLoader (final ClassLoader parent, final File [] classpath,
+                             final IInclExclFilter forcedDelegationFilter,
+                             final IInclExclFilter throughDelegationFilter,
+                             final IClassLoadHook hook, final Map cache)
+        throws MalformedURLException
+    {
+        // setting ClassLoader.parent to null disables the standard delegation
+        // behavior in a few places, including URLClassLoader.getResource():
+        
+        super (filesToURLs (classpath), null);
+        
+        // TODO: arg validation
+        
+        m_hook = hook;
+        m_cache = cache; // can be null
+        
+        m_forcedDelegationFilter = forcedDelegationFilter;
+        m_throughDelegationFilter = throughDelegationFilter;
+         
+        m_parent = parent;        
+        m_bufPool = new PoolEntry [BAOS_POOL_SIZE];
+        
+        m_log = Logger.getLogger ();
+    }
+    
+    /**
+     * Overrides java.lang.ClassLoader.loadClass() to change the usual parent-child
+     * delegation rules just enough to be able to 'replace' the parent loader. This
+     * also has the effect of detecting 'system' classes without doing any class
+     * name-based matching.
+     */
+    public synchronized final Class loadClass (final String name, final boolean resolve)
+        throws ClassNotFoundException
+    {
+        final boolean trace1 = m_log.atTRACE1 ();
+        
+        if (trace1) m_log.trace1 ("loadClass",  "(" + name + ", " + resolve + "): nest level " + m_nestLevel);
+        
+        Class c = null;
+        
+        // first, check if this class has already been defined by this classloader
+        // instance:
+        c = findLoadedClass (name);
+        
+        if (c == null)
+        {
+            Class parentsVersion = null;
+            if (m_parent != null)
+            {
+                try
+                {
+                    parentsVersion = m_parent.loadClass (name); // note: it is important that this does not init the class
+                    
+                    if ((parentsVersion.getClassLoader () != m_parent) ||
+                        ((m_forcedDelegationFilter == null) || m_forcedDelegationFilter.included (name)))
+                    {
+                        // (a) m_parent itself decided to delegate: use parent's version
+                        // (b) the class was on the forced delegation list: use parent's version
+                        c = parentsVersion;
+                        if (trace1) m_log.trace1 ("loadClass", "using parent's version for [" + name + "]");
+                    }
+                }
+                catch (ClassNotFoundException cnfe)
+                {
+                    // if the class was on the forced delegation list, error out: 
+                    if ((m_forcedDelegationFilter == null) || m_forcedDelegationFilter.included (name))
+                        throw cnfe;
+                }
+            }
+            
+            if (c == null)
+            {
+                try
+                {
+                    // either (a) m_parent was null or (b) it could not load 'c'
+                    // or (c) it will define 'c' itself if allowed to. In any
+                    // of these cases I attempt to define my own version:
+                    c = findClass (name);
+                }
+                catch (ClassNotFoundException cnfe)
+                {
+                    // this is a difficult design point unless I resurrect the -lx option
+                    // and document how to use it [which will confuse most users anyway]
+                    
+                    // another alternative would be to see if parent's version is included by
+                    // the filter and print a warning; still, it does not help with JAXP etc 
+                    
+                    if (parentsVersion != null)
+                    {
+                        final boolean delegate = (m_throughDelegationFilter == null) || m_throughDelegationFilter.included (name); 
+                        
+                        if (delegate)
+                        {
+                            c = parentsVersion;
+                            if (trace1) m_log.trace1 ("loadClass", "[delegation filter] using parent's version for [" + name + "]");
+                        }
+                        else
+                            throw cnfe;
+                    }
+                    else
+                      throw cnfe;
+                }
+            }
+        }
+        
+        if (c == null) throw new ClassNotFoundException (name);
+        
+        if (resolve) resolveClass (c); // this never happens in J2SE JVMs
+        return c;
+    }
+    
+    // TODO: remove this in the release build
+    
+    public final URL getResource (final String name)
+    {
+        final boolean trace1 = m_log.atTRACE1 ();
+        
+        if (trace1) m_log.trace1 ("getResource",  "(" + name + "): nest level " + m_nestLevel);
+        
+        final URL result = super.getResource (name);
+        if (trace1 && (result != null)) m_log.trace1 ("loadClass",  "[" + name + "] found in " + result);
+        
+        return result;
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected final Class findClass (final String name)
+        throws ClassNotFoundException
+    {
+        final boolean trace1 = m_log.atTRACE1 ();
+        
+        if (trace1) m_log.trace1 ("findClass",  "(" + name + "): nest level " + m_nestLevel);
+        
+        final boolean useClassCache = (m_cache != null);
+        final ClassPathCacheEntry entry = useClassCache ? (ClassPathCacheEntry) m_cache.remove (name) : null;
+        
+        byte [] bytes;
+        int length;
+        URL classURL = null;
+            
+        if (entry != null) // cache hit
+        {
+            ++ m_cacheHits;
+            
+            // used cached class def bytes, no need to repeat disk I/O:
+            
+            try
+            {
+                classURL = new URL (entry.m_srcURL);
+            }
+            catch (MalformedURLException murle) // this should never happen
+            {
+                if ($assert.ENABLED)
+                {
+                    murle.printStackTrace (System.out);
+                }
+            }
+            
+            PoolEntry buf = null;
+            try
+            {
+                buf = acquirePoolEntry ();
+                final ByteArrayOStream baos = buf.m_baos; // reset() has been called on this
+                
+                // the original class definition:
+                bytes = entry.m_bytes;
+                length = bytes.length;
+                
+                if ((m_hook != null) && m_hook.processClassDef (name, bytes, length, baos)) // note: this can overwrite 'bytes'
+                {
+                    // the instrumented class definition:
+                    bytes = baos.getByteArray ();
+                    length = baos.size ();
+                    
+                    if (trace1) m_log.trace1 ("findClass",  "defining [cached] instrumented [" + name + "] {" + length + " bytes }");
+                }
+                else
+                {
+                    if (trace1) m_log.trace1 ("findClass",  "defining [cached] [" + name + "] {" + length + " bytes }");
+                }
+                
+                return defineClass (name, bytes, length, classURL);
+            }
+            catch (IOException ioe)
+            {
+                throw new ClassNotFoundException (name);
+            }
+            finally
+            {
+                if (buf != null) releasePoolEntry (buf);
+            }
+        }
+        else // cache miss
+        {
+            if (useClassCache) ++ m_cacheMisses;
+            
+            // .class files are not guaranteed to be loadable as resources;
+            // but if Sun's code does it...
+            final String classResource = name.replace ('.', '/') + ".class";
+            
+            // even thought normal delegation is disabled, this will find bootstrap classes:
+            classURL = getResource (classResource); // important to hook into URLClassLoader's overload of this so that Class-Path manifest attributes are processed etc
+            
+            if (trace1 && (classURL != null)) m_log.trace1 ("findClass",  "[" + name + "] found in " + classURL);
+            
+            if (classURL == null)
+                throw new ClassNotFoundException (name);
+            else
+            {
+                InputStream in = null;
+                PoolEntry buf = null;
+                try
+                {
+                    in = classURL.openStream ();
+                    
+                    buf = acquirePoolEntry ();
+                    final ByteArrayOStream baos = buf.m_baos; // reset() has been called on this
+                    
+                    readFully (in, baos, buf.m_buf);
+                    in.close (); // don't keep the file handle across reentrant calls
+                    in = null;
+                    
+                    // the original class definition:
+                    bytes = baos.getByteArray ();
+                    length = baos.size ();
+                    
+                    baos.reset (); // reuse this for processClassDef below
+                    
+                    if ((m_hook != null) && m_hook.processClassDef (name, bytes, length, baos)) // note: this can overwrite 'bytes'
+                    {
+                        // the instrumented class definition:
+                        bytes = baos.getByteArray ();
+                        length = baos.size ();
+                        
+                        if (trace1) m_log.trace1 ("findClass",  "defining instrumented [" + name + "] {" + length + " bytes }");
+                    }
+                    else
+                    {
+                        if (trace1) m_log.trace1 ("findClass",  "defining [" + name + "] {" + length + " bytes }");
+                    }
+                    
+                    return defineClass (name, bytes, length, classURL);
+                }
+                catch (IOException ioe)
+                {
+                    throw new ClassNotFoundException (name);
+                }
+                finally
+                {
+                    if (buf != null) releasePoolEntry (buf);
+                    if (in != null) try { in.close (); } catch (Exception ignore) {}
+                }
+            }
+        }
+    }
+    
+    public void debugDump (final PrintWriter out)
+    {
+        if (out != null)
+        {
+            out.println (this + ": " + m_cacheHits + " class cache hits, " + m_cacheMisses + " misses");
+        }
+    }
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class PoolEntry
+    {
+        PoolEntry (final int baosCapacity, final int bufSize)
+        {
+            m_baos = new ByteArrayOStream (baosCapacity);
+            m_buf = new byte [bufSize];
+        }
+        
+        void trim (final int baosCapacity, final int baosMaxCapacity)
+        {
+            if (m_baos.capacity () > baosMaxCapacity)
+            {
+                m_baos = new ByteArrayOStream (baosCapacity);
+            }
+        }
+        
+        ByteArrayOStream m_baos;
+        final byte [] m_buf;
+        
+    } // end of nested class
+    
+    
+    /*
+     * 'srcURL' may be null
+     */
+    private Class defineClass (final String className, final byte [] bytes, final int length, final URL srcURL)
+    {
+        // support ProtectionDomains with non-null class source URLs:
+        // [however, disable anything related to sealing or signing]
+        
+        final CodeSource csrc = new CodeSource (srcURL, null);
+        
+        // allow getPackage() to return non-null on the class we are about to
+        // define (however, don't bother emulating the original manifest info since
+        // we may be altering manifest content anyway):
+        
+        final int lastDot = className.lastIndexOf ('.');
+        if (lastDot >= 0)
+        {
+            final String packageName = className.substring (0, lastDot);
+            
+            final Package pkg = getPackage (packageName);
+            if (pkg == null)
+            {
+                definePackage (packageName,
+                               IAppConstants.APP_NAME, IAppConstants.APP_VERSION, IAppConstants.APP_COPYRIGHT,
+                               IAppConstants.APP_NAME, IAppConstants.APP_VERSION, IAppConstants.APP_COPYRIGHT,
+                               srcURL);
+            }
+        }
+        
+        return defineClass (className, bytes, 0, length, csrc);
+    }
+    
+    
+    private static URL [] filesToURLs (final File [] classpath)
+        throws MalformedURLException
+    {
+        if ((classpath == null) || (classpath.length == 0))
+            return EMPTY_URL_ARRAY;
+            
+        final URL [] result = new URL [classpath.length];
+        
+        for (int f = 0; f < result.length ; ++ f)
+        {
+            result [f] = classpath [f].toURL (); // note: this does proper dir encoding
+        }
+        
+        return result;
+    }
+    
+    /**
+     * Reads the entire contents of a given stream into a flat byte array.
+     */
+    private static void readFully (final InputStream in, final ByteArrayOStream out, final byte [] buf)
+        throws IOException
+    {
+        for (int read; (read = in.read (buf)) >= 0; )
+        {
+            out.write (buf, 0, read);
+        }
+    }
+    
+    /*
+     * not MT-safe; must be called from loadClass() only
+     */
+    private PoolEntry acquirePoolEntry ()
+    {
+        PoolEntry result;
+        
+        if (m_nestLevel >= BAOS_POOL_SIZE)
+        {
+            result = new PoolEntry (BAOS_INIT_SIZE, BAOS_INIT_SIZE);
+        }
+        else
+        {
+            result = m_bufPool [m_nestLevel];
+            if (result == null)
+            {
+                result = new PoolEntry (BAOS_INIT_SIZE, BAOS_INIT_SIZE);
+                m_bufPool [m_nestLevel] = result;
+            }
+            else
+            {
+                result.m_baos.reset ();
+            }
+        }
+        
+        ++ m_nestLevel;
+            
+        return result;
+    }
+    
+    /*
+     * not MT-safe; must be called from loadClass() only
+     */
+    private void releasePoolEntry (final PoolEntry buf)
+    {
+        if (-- m_nestLevel < BAOS_POOL_SIZE)
+        {
+            buf.trim (BAOS_INIT_SIZE, BAOS_MAX_SIZE);
+        }
+    }
+    
+    
+    private final ClassLoader m_parent;    
+    
+    private final IInclExclFilter m_forcedDelegationFilter;
+    private final IInclExclFilter m_throughDelegationFilter;
+    
+    private final Map /* classJavaName:String -> ClassPathCacheEntry */ m_cache; // can be null
+    private final IClassLoadHook m_hook;
+    private final PoolEntry [] m_bufPool;
+    
+    private final Logger m_log; // a loader instance is used concurrently but cached its log config at construction time
+    
+    private int m_nestLevel;
+    
+    private int m_cacheHits, m_cacheMisses;
+    
+    private static final int BAOS_INIT_SIZE = 32 * 1024;
+    private static final int BAOS_MAX_SIZE = 1024 * 1024;
+    private static final int BAOS_POOL_SIZE = 8;
+    private static final URL [] EMPTY_URL_ARRAY = new URL [0];
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/RT.java b/core/java12/com/vladium/emma/rt/RT.java
new file mode 100644
index 0000000..9273348
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/RT.java
@@ -0,0 +1,255 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: RT.java,v 1.2.2.3 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.File;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.IProperties;
+import com.vladium.util.Property;
+import com.vladium.util.exit.ExitHookManager;
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.EMMAProperties;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.emma.data.DataFactory;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class RT implements IAppConstants
+{
+    // public: ................................................................
+    
+    
+    public static synchronized ICoverageData reset (final boolean createCoverageData, final boolean createExitHook)
+    {
+        // reload the app properties [needs to be done to accomodate classloader rearrangements]:
+        
+        // avoid the call context tricks at runtime in case security causes problems,
+        // use an explicit caller parameter for getAppProperties():
+        
+        ClassLoader loader = RT.class.getClassLoader ();
+        if (loader == null) loader = ClassLoader.getSystemClassLoader (); 
+        
+        IProperties appProperties = null;
+        try
+        {
+            appProperties = EMMAProperties.getAppProperties (loader);
+        }
+        catch (Throwable t)
+        {
+            // TODO: handle better
+            t.printStackTrace (System.out);
+        }
+        s_appProperties = appProperties;
+
+
+        if (EXIT_HOOK_MANAGER != null)
+        {
+            // disable/remove the current hook, if any:
+            
+            if (s_exitHook != null)
+            {
+                 // note: no attempt is made to execute the existing hook, so its coverage
+                 // data may be simply discarded
+                
+                EXIT_HOOK_MANAGER.removeExitHook (s_exitHook);
+                s_exitHook = null;
+            }
+        }
+        
+        ICoverageData cdata = s_cdata; // no sync accessor needed
+        if (createCoverageData)
+        {
+            cdata = DataFactory.newCoverageData ();
+            s_cdata = cdata;
+        }
+        else
+        {
+            s_cdata = null;
+        }
+        
+        if (EXIT_HOOK_MANAGER != null)
+        {
+            if (createExitHook && (cdata != null))
+            {
+                final Runnable exitHook = new RTExitHook (RT.class, cdata, getCoverageOutFile (), getCoverageOutMerge ());
+
+                // FR SF978671: fault all classes that we might need to do coverage
+                // data dumping (this forces classdefs to be loaded into classloader
+                // class cache and allows output file writing to succeed even if
+                // the RT classloader is some component loader (e.g, in a J2EE container)
+                // that gets invalidated by the time the exit hook thread is run:
+                
+                RTExitHook.createClassLoaderClosure ();
+                
+                if (EXIT_HOOK_MANAGER.addExitHook (exitHook))
+                {
+                    s_exitHook = exitHook;
+                }
+                // else TODO: log/warn
+            }
+        }
+        
+        return cdata;
+    }
+    
+    public static void r (final boolean [][] coverage, final String classVMName, final long stamp)
+    {
+        // note that we use class names, not the actual Class objects, as the keys here. This
+        // is not the best possible solution because it is not capable of supporting
+        // multiply (re)loaded classes within the same app, but the rest of the toolkit
+        // isn't designed to support this anyway. Furthermore, this does not interfere
+        // with class unloading.
+
+        final ICoverageData cdata = getCoverageData (); // need to use accessor for JMM reasons
+
+        // ['cdata' can be null if a previous call to dumpCoverageData() disabled data collection]
+        
+        if (cdata != null)
+        {
+            synchronized (cdata.lock ())
+            {
+                // TODO: could something useful be communicated back to the class
+                // by returning something here [e.g., unique class ID (solves the
+                // issues of class name collisions and class reloading) or RT.class
+                // (to prevent RT reloading)]
+                
+                cdata.addClass (coverage, classVMName, stamp);
+            }
+        }
+    }
+
+    public static synchronized ICoverageData getCoverageData ()
+    {
+        return s_cdata;
+    }
+    
+    public static synchronized IProperties getAppProperties ()
+    {
+        return s_appProperties;
+    }
+    
+    /**
+     * Public API for forcing coverage data dump.
+     * 
+     * @param outFile
+     * @param merge
+     * @param stopDataCollection
+     */
+    public static synchronized void dumpCoverageData (File outFile, final boolean merge, final boolean stopDataCollection)
+    {
+        if (DEBUG) System.out.println ("RT::dumpCoverageData() DUMPING " + RT.class.getClassLoader ());
+        outFile = outFile != null ? outFile : getCoverageOutFile ();
+        
+        ICoverageData cdata = s_cdata; // no need to use accessor
+        if (stopDataCollection) s_cdata = null; // TODO: log this NOTE: this does not really stop data collection, merely prevents new class registration
+        
+        RTCoverageDataPersister.dumpCoverageData (cdata, ! stopDataCollection, outFile, merge);
+    }
+    
+    public static synchronized void dumpCoverageData (File outFile, final boolean stopDataCollection)
+    {
+        outFile = outFile != null ? outFile : getCoverageOutFile ();
+        
+        ICoverageData cdata = s_cdata; // no need to use accessor
+        if (stopDataCollection) s_cdata = null; // TODO: log this NOTE: this does not really stop data collection, merely prevents new class registration
+        
+        RTCoverageDataPersister.dumpCoverageData (cdata, ! stopDataCollection, outFile, getCoverageOutMerge ());
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+    
+    private RT () {} // prevent subclassing
+    
+    
+    private static File getCoverageOutFile ()
+    {
+        final IProperties appProperties = getAppProperties (); // sync accessor
+        if (appProperties != null)
+        {
+            final String property = appProperties.getProperty (EMMAProperties.PROPERTY_COVERAGE_DATA_OUT_FILE,
+                                                               EMMAProperties.DEFAULT_COVERAGE_DATA_OUT_FILE);
+            return new File (property);
+        }
+        
+        return new File (EMMAProperties.DEFAULT_COVERAGE_DATA_OUT_FILE); 
+    }
+    
+    private static boolean getCoverageOutMerge ()
+    {
+        final IProperties appProperties = getAppProperties (); // sync accessor
+        if (appProperties != null)
+        {
+            // [Boolean.toString (boolean) is J2SDK 1.4+]
+            
+            final String property = appProperties.getProperty (EMMAProperties.PROPERTY_COVERAGE_DATA_OUT_MERGE,
+                                                               EMMAProperties.DEFAULT_COVERAGE_DATA_OUT_MERGE.toString ());
+            return Property.toBoolean (property);
+        }
+        
+        return EMMAProperties.DEFAULT_COVERAGE_DATA_OUT_MERGE.booleanValue ();
+    }
+    
+        
+    private static ICoverageData s_cdata;
+    private static Runnable s_exitHook;
+    private static IProperties s_appProperties; // TODO: this is better of as java.util.Properties
+
+    private static final ExitHookManager EXIT_HOOK_MANAGER; // set in <clinit>
+    
+    private static final boolean DEBUG = false;
+    
+    static
+    {
+        if (DEBUG) System.out.println ("RT[" + System.identityHashCode (RT.class) + "]::<clinit>: loaded by " + RT.class.getClassLoader ());
+        
+        ExitHookManager temp = null;
+        try
+        {
+            temp = ExitHookManager.getSingleton ();
+        }
+        catch (Throwable t)
+        {
+            // TODO: handle better
+            t.printStackTrace (System.out);
+        }
+        EXIT_HOOK_MANAGER = temp;
+
+         
+        if (RTSettings.isStandaloneMode ())
+        {
+            if (DEBUG) System.out.println ("RT::<clinit>: STANDALONE MODE");
+            
+            // load app props, create coverage data, and register an exit hook for it:
+            reset (true, true);
+            
+            // use method-scoped loggers in RT:
+            final Logger log = Logger.getLogger ();
+            if (log.atINFO ())
+            {
+                log.info ("collecting runtime coverage data ...");
+            }
+        }
+        else
+        {
+            // load app props only:
+            reset (false, false);
+        }
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/RTCoverageDataPersister.java b/core/java12/com/vladium/emma/rt/RTCoverageDataPersister.java
new file mode 100644
index 0000000..dfc7ed6
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/RTCoverageDataPersister.java
@@ -0,0 +1,78 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: RTCoverageDataPersister.java,v 1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.File;
+
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.data.DataFactory;
+import com.vladium.emma.data.ICoverageData;
+import com.vladium.logging.Logger;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2004
+ */
+abstract
+class RTCoverageDataPersister
+{
+    // public: ................................................................
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    /*
+     * Stateless package-private method shared by RT and RTExitHook for coverage
+     * data persistence. This method was moved out of RT class after build 4120
+     * in order to decrease classloading dependency set for RTExitHook
+     * (FR SF978671).
+     */
+    static void dumpCoverageData (final ICoverageData cdata, final boolean useSnapshot,
+                                  final File outFile, final boolean merge)
+    {
+        try
+        {
+            if (cdata != null)
+            {
+                // use method-scoped loggers everywhere in RT:
+                final Logger log = Logger.getLogger ();
+                final boolean info = log.atINFO ();
+                
+                final long start = info ? System.currentTimeMillis () : 0;
+                {
+                    final ICoverageData cdataView = useSnapshot ? cdata.shallowCopy () : cdata;
+                    
+                    synchronized (Object.class) // fake a JVM-global critical section when multilply loaded RT's write to the same file
+                    {
+                        DataFactory.persist (cdataView, outFile, merge);
+                    }
+                }
+                if (info)
+                {
+                    final long end = System.currentTimeMillis ();
+                    
+                    log.info ("runtime coverage data " + (merge ? "merged into" : "written to") + " [" + outFile.getAbsolutePath () + "] {in " + (end - start) + " ms}");
+                }
+            }
+        }
+        catch (Throwable t)
+        {
+            // log
+            t.printStackTrace ();
+            
+            // TODO: do better chaining in JRE 1.4+
+            throw new RuntimeException (IAppConstants.APP_NAME + " failed to dump coverage data: " + t.toString ());
+        }
+    }
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/RTExitHook.java b/core/java12/com/vladium/emma/rt/RTExitHook.java
new file mode 100644
index 0000000..56b6d10
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/RTExitHook.java
@@ -0,0 +1,126 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: RTExitHook.java,v 1.1.1.1.2.2 2004/07/10 03:34:53 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import com.vladium.emma.data.ICoverageData;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+final class RTExitHook implements Runnable
+{
+    // public: ................................................................
+    
+    
+    public synchronized void run ()
+    {
+        if (m_cdata != null)
+        {
+            RTCoverageDataPersister.dumpCoverageData (m_cdata, true, m_outFile, m_merge);
+            
+            m_RT = null;
+            m_cdata = null;
+        }
+    }
+    
+    public static void createClassLoaderClosure ()
+    {
+        Properties closureMap = null;
+        
+        InputStream in = null;
+        try
+        {
+            // note that this does not use ClassLoaderResolver by design
+            // (closure loading must not load any app classes that are outside
+            /// the closure list)
+            
+            in = RTExitHook.class.getResourceAsStream (CLOSURE_RESOURCE);
+            if (in != null)
+            {
+                closureMap = new Properties ();
+                closureMap.load (in);
+            }
+            else
+            {
+                throw new Error ("packaging failure: closure resource not found");
+            }
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace (System.out);
+            
+            throw new Error ("packaging failure: " + e.toString ());
+        }
+        finally
+        {
+            if (in != null) try { in.close (); } catch (IOException ignore) { ignore.printStackTrace (); }
+        }
+        in = null;
+        
+        final String closureList = closureMap.getProperty ("closure");
+        if (closureList == null)
+        {
+            throw new Error ("packaging failure: no closure mapping");
+        }
+        
+        // note that this uses the current classloader (only), consistently
+        // with the getResourceAsStream() above:
+        
+        final ClassLoader loader = RTExitHook.class.getClassLoader ();
+        
+        final StringTokenizer tokenizer = new StringTokenizer (closureList, ",");
+        while (tokenizer.hasMoreTokens ())
+        {
+            final String className = tokenizer.nextToken ();
+            
+            try
+            {
+                Class.forName (className, true, loader);
+            }
+            catch (Exception e)
+            {
+                throw new Error ("packaging failure: class [" + className + "] not found {" + e.toString () + "}");
+            }
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    RTExitHook (final Class RT, final ICoverageData cdata, final File outFile, final boolean merge)
+    {
+        m_RT = RT;
+        m_cdata = cdata;
+        
+        m_outFile = outFile;
+        m_merge = merge;
+    }
+        
+    // private: ...............................................................
+
+
+    private final File m_outFile;
+    private final boolean m_merge;
+    
+    private Class m_RT; // keep our RT class pinned in memory
+    private ICoverageData m_cdata;
+    
+    private static final String CLOSURE_RESOURCE = "RTExitHook.closure"; // relative to this package
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/rt/RTSettings.java b/core/java12/com/vladium/emma/rt/RTSettings.java
new file mode 100644
index 0000000..8b37f53
--- /dev/null
+++ b/core/java12/com/vladium/emma/rt/RTSettings.java
@@ -0,0 +1,46 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: RTSettings.java,v 1.1.1.1 2004/05/09 16:57:44 vlad_r Exp $
+ */
+package com.vladium.emma.rt;
+
+// ----------------------------------------------------------------------------
+/**
+ * Conceptually, this class is an extention of class RT. This is a separate class,
+ * however, to help load RT in a lazy manner.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class RTSettings
+{
+    // public: ................................................................
+    
+    
+    public static synchronized boolean isStandaloneMode ()
+    {
+        return ! s_not_standalone;
+    }
+    
+    public static synchronized void setStandaloneMode (final boolean standalone)
+    {
+        s_not_standalone = ! standalone;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private RTSettings () {} // prevent subclassing
+        
+    private static boolean s_not_standalone;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/emma/runCommand.java b/core/java12/com/vladium/emma/runCommand.java
new file mode 100644
index 0000000..88ffe8f
--- /dev/null
+++ b/core/java12/com/vladium/emma/runCommand.java
@@ -0,0 +1,332 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: runCommand.java,v 1.1.1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $
+ */
+package com.vladium.emma;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import com.vladium.util.ClassLoaderResolver;
+import com.vladium.util.Strings;
+import com.vladium.util.args.IOptsParser;
+import com.vladium.util.asserts.$assert;
+import com.vladium.emma.rt.AppRunner;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class runCommand extends Command
+{
+    // public: ................................................................
+
+
+    public synchronized void run ()
+    {
+        ClassLoader loader;
+        try
+        {
+            loader = ClassLoaderResolver.getClassLoader ();
+        }
+        catch (Throwable t)
+        {
+            loader = getClass ().getClassLoader ();
+        }
+        
+        try
+        {
+            // process 'args':
+            {
+                final IOptsParser parser = getOptParser (loader);
+                final IOptsParser.IOpts parsedopts = parser.parse (m_args);
+                
+                // check if usage is requested before checking args parse errors etc:
+                {
+                    final int usageRequestLevel = parsedopts.usageRequestLevel ();
+
+                    if (usageRequestLevel > 0)
+                    {
+                        usageexit (parser, usageRequestLevel, null);
+                        return;
+                    }
+                }
+                
+                final IOptsParser.IOpt [] opts = parsedopts.getOpts ();
+                
+                if (opts == null) // this means there were args parsing errors
+                {
+                    parsedopts.error (m_out, STDOUT_WIDTH);
+                    usageexit (parser, IOptsParser.SHORT_USAGE, null);
+                    return;
+                }
+                
+                // process parsed args:
+                try
+                {
+                    for (int o = 0; o < opts.length; ++ o)
+                    {
+                        final IOptsParser.IOpt opt = opts [o];
+                        final String on = opt.getCanonicalName ();
+                        
+                        if (! processOpt (opt))
+                        {
+                            if ("cp".equals (on))
+                            {
+                                m_classpath = getListOptValue (opt, PATH_DELIMITERS, true);
+                            }
+                            else if ("jar".equals (on))
+                            {
+                                m_jarMode = true;
+                            }
+                            else if ("f".equals (on))
+                            {
+                                m_scanCoveragePath = getOptionalBooleanOptValue (opt);
+                            }
+                            else if ("sp".equals (on))
+                            {
+                                m_srcpath = getListOptValue (opt, PATH_DELIMITERS, true);
+                            }
+                            else if ("raw".equals (on))
+                            {
+                                m_dumpRawData = getOptionalBooleanOptValue (opt); 
+                            }
+                            else if ("out".equals (on))
+                            {
+                                m_outFileName = opt.getFirstValue ();
+                            }
+                            else if ("merge".equals (on))
+                            {
+                                m_outDataMerge = getOptionalBooleanOptValue (opt) ? Boolean.TRUE : Boolean.FALSE; 
+                            }
+                            else if ("r".equals (on))
+                            {
+                                m_reportTypes = Strings.merge (opt.getValues (), COMMA_DELIMITERS, true);
+                            }
+                            else if ("ix".equals (on))
+                            {
+                                m_ixpath = getListOptValue (opt, COMMA_DELIMITERS, true);
+                            }
+                        }
+                    }
+                    
+                    // user '-props' file property overrides:
+                    
+                    if (! processFilePropertyOverrides ()) return;
+                    
+                    // process prefixed opts:
+                    
+                    processCmdPropertyOverrides (parsedopts);
+                }
+                catch (IOException ioe)
+                {
+                    throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
+                }
+                
+                
+                // process free args:
+                {
+                    final String [] freeArgs = parsedopts.getFreeArgs ();
+                    
+                    if (m_jarMode)
+                    {
+                        if ((freeArgs == null) || (freeArgs.length == 0))
+                        {
+                            usageexit (parser, IOptsParser.SHORT_USAGE, "missing jar file name");
+                            return;
+                        }
+                        
+                        if ($assert.ENABLED) $assert.ASSERT (freeArgs != null && freeArgs.length > 0, "invalid freeArgs");
+    
+                        final File jarfile = new File (freeArgs [0]);
+                        final String jarMainClass;
+                        try
+                        {
+                            jarMainClass = openJarFile (jarfile); // the rest of free args are *not* ignored
+                        }
+                        catch (IOException ioe)
+                        {
+                            // TODO: is the right error code?
+                            throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
+                        }                    
+                        
+                        if (jarMainClass == null)
+                        {
+                            exit (true, "failed to load Main-Class manifest attribute from [" + jarfile.getAbsolutePath () + "]", null, RC_UNEXPECTED);
+                            return; 
+                        }
+                        
+                        if ($assert.ENABLED) $assert.ASSERT (jarMainClass != null, "invalid jarMainClass");
+                        
+                        m_appArgs = new String [freeArgs.length];
+                        System.arraycopy (freeArgs, 1, m_appArgs, 1, freeArgs.length - 1);
+                        m_appArgs [0] = jarMainClass;
+                        
+                        m_classpath = new String [] { jarfile.getPath () };
+                    }
+                    else
+                    {
+                        if ((freeArgs == null) || (freeArgs.length == 0))
+                        {
+                            usageexit (parser, IOptsParser.SHORT_USAGE, "missing application class name");
+                            return;
+                        }
+                        
+                        m_appArgs = freeArgs;
+                    }
+                }
+                // [m_appArgs: { mainclass, arg1, arg2, ... }]
+
+                
+                // handle cmd line-level defaults and option interaction
+                {
+                    if (DEFAULT_TO_SYSTEM_CLASSPATH)
+                    {
+                        if (m_classpath == null)
+                        {
+                            // TODO" this is not guaranteed to work (in WebStart etc), so double check if I should remove this
+                            
+                            final String systemClasspath = System.getProperty ("java.class.path", "");
+                            if (systemClasspath.length () == 0)
+                            {
+                                // TODO: assume "." just like Sun JVMs in this case?
+                                usageexit (parser, IOptsParser.SHORT_USAGE, "could not infer coverage classpath from 'java.class.path'; use an explicit -cp option");
+                                return;
+                            }
+                            
+                            m_classpath = new String [] {systemClasspath};
+                        }
+                    }
+                    else
+                    {
+                        if (m_classpath == null)
+                        {
+                            usageexit (parser, IOptsParser.SHORT_USAGE, "either '-cp' or '-jar' option is required");
+                            return;
+                        }
+                    }               
+
+                    // TODO: who owns setting this 'txt' default? most likely AppRunner
+                    if (m_reportTypes == null)
+                    {
+                        m_reportTypes = new String [] {"txt"};
+                    }
+                }
+            }
+            
+            // run the app:
+            {
+                if ($assert.ENABLED) $assert.ASSERT (m_appArgs != null && m_appArgs.length > 0, "invalid m_appArgs");
+                
+                final String [] appargs = new String [m_appArgs.length - 1];
+                System.arraycopy (m_appArgs, 1, appargs, 0, appargs.length);
+                
+                final AppRunner processor = AppRunner.create (loader);
+                processor.setAppName (IAppConstants.APP_NAME); // for log prefixing
+                
+                processor.setAppClass (m_appArgs [0], appargs);
+                processor.setCoveragePath (m_classpath, true); // TODO: an option to set 'canonical'?
+                processor.setScanCoveragePath (m_scanCoveragePath);
+                processor.setSourcePath (m_srcpath);
+                processor.setInclExclFilter (m_ixpath);
+                processor.setDumpSessionData (m_dumpRawData);
+                processor.setSessionOutFile (m_outFileName);
+                processor.setSessionOutMerge (m_outDataMerge);
+                if ($assert.ENABLED) $assert.ASSERT (m_reportTypes != null, "m_reportTypes no set");
+                processor.setReportTypes (m_reportTypes);
+                processor.setPropertyOverrides (m_propertyOverrides);
+                
+                processor.run ();
+            }
+        }
+        catch (EMMARuntimeException yre)
+        {
+            // TODO: see below
+            
+            exit (true, yre.getMessage (), yre, RC_UNEXPECTED); // does not return
+            return;
+        }
+        catch (Throwable t)
+        {
+            // TODO: embed: OS/JVM fingerprint, build #, etc
+            // TODO: save stack trace in a file and prompt user to send it to ...
+            
+            exit (true, "unexpected failure: ", t, RC_UNEXPECTED); // does not return
+            return;
+        }
+
+        exit (false, null, null, RC_OK);
+    }
+    
+    // protected: .............................................................
+
+
+    protected runCommand (final String usageToolName, final String [] args)
+    {
+        super (usageToolName, args);
+    }
+
+    protected void initialize ()
+    {
+        // TODO: clean up instance state
+        
+        super.initialize ();
+    }
+    
+    protected String usageArgsMsg ()
+    {
+        return "[options] class [args...] | -jar [options] jarfile [args...]";
+    }
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static String openJarFile (final File file)
+        throws IOException
+    {
+        JarFile jarfile = null;
+        try
+        {
+            jarfile = new JarFile (file, false);
+            
+            final Manifest manifest = jarfile.getManifest ();
+            if (manifest == null) return null;
+            
+            final Attributes attributes = manifest.getMainAttributes ();
+            if (attributes == null) return null;
+
+            final String jarMainClass = attributes.getValue (Attributes.Name.MAIN_CLASS);
+            
+            return jarMainClass;
+        }
+        finally
+        {
+            if (jarfile != null) try { jarfile.close (); } catch (IOException ignore) {}
+        }
+    }
+        
+        
+    private String [] m_classpath, m_srcpath;
+    private boolean m_jarMode;
+    private boolean m_scanCoveragePath; // defaults to false
+    private String [] m_ixpath;
+    private String [] m_appArgs;
+    private boolean m_dumpRawData; // defaults to false
+    private String m_outFileName;
+    private Boolean m_outDataMerge; 
+    private String [] m_reportTypes;
+    
+    private static final boolean DEFAULT_TO_SYSTEM_CLASSPATH = false;
+        
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/jcd/cls/AbstractClassDefVisitor.java b/core/java12/com/vladium/jcd/cls/AbstractClassDefVisitor.java
new file mode 100644
index 0000000..4b429ac
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/AbstractClassDefVisitor.java
@@ -0,0 +1,65 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AbstractClassDefVisitor.java,v 1.1.1.1 2004/05/09 16:57:44 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class AbstractClassDefVisitor implements IClassDefVisitor
+{
+    // public: ................................................................
+
+    // IClassDefVisitor:
+
+    public Object visit (final ClassDef cls, final Object ctx)
+    {
+        visit (cls.getConstants (), ctx);
+        visit (cls.getInterfaces (), ctx);
+        visit (cls.getFields (), ctx);
+        visit (cls.getMethods (), ctx);
+        visit (cls.getAttributes (), ctx);
+        
+        return ctx;
+    }
+
+    public Object visit (final IAttributeCollection attributes, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final IConstantCollection constants, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final IFieldCollection fields, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final IInterfaceCollection interfaces, final Object ctx)
+    {
+        return ctx;
+    }
+
+    public Object visit (final IMethodCollection methods, final Object ctx)
+    {
+        return ctx;
+    }
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/AttributeCollection.java b/core/java12/com/vladium/jcd/cls/AttributeCollection.java
new file mode 100644
index 0000000..ed24d3e
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/AttributeCollection.java
@@ -0,0 +1,217 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AttributeCollection.java,v 1.1.1.1 2004/05/09 16:57:44 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.cls.attribute.*;
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+final class AttributeCollection implements IAttributeCollection
+{
+    // public: ................................................................
+    
+    // TODO: extend ItemCollection into all XXXCollection classes ?
+    
+
+    // ACCESSORS:
+        
+    public final Attribute_info get (final int offset)
+    {
+        return (Attribute_info) m_attributes.get (offset);
+    }
+    
+    public final boolean hasSynthetic ()
+    {
+        return m_syntheticRefCount > 0;
+    }
+    
+    public final boolean hasBridge ()
+    {
+        return m_bridgeRefCount > 0;
+    }
+    
+    public final InnerClassesAttribute_info getInnerClassesAttribute ()
+    {
+        final int innerClassesAttributeOffset = m_innerClassesAttributeOffset;
+        if (innerClassesAttributeOffset < 0)
+            return null;
+        else
+            return (InnerClassesAttribute_info) get (innerClassesAttributeOffset);
+    }
+    
+    public final int size ()
+    {
+        return m_attributes.size (); 
+    }
+    
+    public final long length ()
+    {
+        // TODO: cache?
+        
+        long result = 2;
+        
+        int _attributes_count = m_attributes.size (); // use size() if this class becomes non-final
+        for (int i = 0; i < _attributes_count; i++) result += get (i).length ();
+        
+        return result;
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final AttributeCollection _clone = (AttributeCollection) super.clone ();
+            
+            // deep clone:
+            
+            final int attributes_count = m_attributes.size (); // use size() if this class becomes non-final
+            _clone.m_attributes = new ArrayList (attributes_count);
+            for (int a = 0; a < attributes_count; ++ a)
+            {
+                _clone.m_attributes.add (((Attribute_info) m_attributes.get (a)).clone ());
+            }
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        int attributes_count = size ();
+        out.writeU2 (attributes_count);
+        
+        for (int i = 0; i < attributes_count; i++)
+        {
+            get (i).writeInClassFormat (out);
+        }
+    }
+
+    // Visitor:
+    
+    public void accept (final IClassDefVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+
+
+    // MUTATORS:
+
+    public int add (final Attribute_info attribute)
+    {
+        final List/* Attribute_info */ attributes = m_attributes;
+        
+        final int result = attributes.size ();
+        attributes.add (attribute);
+        
+        if (attribute instanceof SyntheticAttribute_info)
+            ++ m_syntheticRefCount;
+        else if (attribute instanceof InnerClassesAttribute_info)
+        {
+            if (m_innerClassesAttributeOffset >= 0)
+                throw new IllegalArgumentException ("this attribute collection already has an InnerClasses attribute");
+            
+            m_innerClassesAttributeOffset = result;
+        }
+        else if (attribute instanceof BridgeAttribute_info)
+            ++ m_bridgeRefCount;
+            
+        if (DISALLOW_MULTIPLE_SYNTHETIC_ATTRIBUTES && $assert.ENABLED)
+            $assert.ASSERT (m_syntheticRefCount >= 0 && m_syntheticRefCount <= 1,
+            "bad synthetic attribute count: " + m_syntheticRefCount);
+        
+        return result;
+    }
+    
+    public Attribute_info set (final int offset, final Attribute_info attribute)
+    {
+        final Attribute_info result = (Attribute_info) m_attributes.set (offset, attribute);
+         
+        if (result instanceof SyntheticAttribute_info)
+            -- m_syntheticRefCount;
+        else if (result instanceof InnerClassesAttribute_info)
+            m_innerClassesAttributeOffset = -1;
+        else if (result instanceof BridgeAttribute_info)
+            -- m_bridgeRefCount;
+            
+        if (attribute instanceof SyntheticAttribute_info)
+            ++ m_syntheticRefCount;
+        else if (attribute instanceof InnerClassesAttribute_info)
+            m_innerClassesAttributeOffset = offset;
+        else if (attribute instanceof BridgeAttribute_info)
+            ++ m_bridgeRefCount;
+            
+        if (DISALLOW_MULTIPLE_SYNTHETIC_ATTRIBUTES && $assert.ENABLED)
+            $assert.ASSERT (m_syntheticRefCount >= 0 && m_syntheticRefCount <= 1,
+            "bad synthetic attribute count: " + m_syntheticRefCount);
+            
+        return result; 
+    }
+    
+    public Attribute_info remove (final int offset)
+    {
+        final Attribute_info result = (Attribute_info) m_attributes.remove (offset);
+        
+        if (result instanceof SyntheticAttribute_info)
+            -- m_syntheticRefCount;
+        else if (result instanceof InnerClassesAttribute_info)
+            m_innerClassesAttributeOffset = -1;
+        else if (result instanceof BridgeAttribute_info)
+            -- m_bridgeRefCount;
+            
+        if (DISALLOW_MULTIPLE_SYNTHETIC_ATTRIBUTES && $assert.ENABLED)
+            $assert.ASSERT (m_syntheticRefCount >= 0 && m_syntheticRefCount <= 1,
+            "bad synthetic attribute count: " + m_syntheticRefCount);
+            
+        return result;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    AttributeCollection (final int capacity)
+    {
+        m_attributes = capacity < 0 ? new ArrayList () : new ArrayList (capacity);
+        m_innerClassesAttributeOffset = -1;
+    }
+
+    // private: ...............................................................
+
+    
+    private List/* Attribute_info */ m_attributes; // never null
+    private transient int m_syntheticRefCount, m_bridgeRefCount;
+    private transient int m_innerClassesAttributeOffset;
+    
+    // note: the spec does not disallow multiple synthetic attributes
+    private static final boolean DISALLOW_MULTIPLE_SYNTHETIC_ATTRIBUTES = false;
+    
+    // note: the spec disallows multiple inner classes attributes
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/ClassDef.java b/core/java12/com/vladium/jcd/cls/ClassDef.java
new file mode 100644
index 0000000..bf26257
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/ClassDef.java
@@ -0,0 +1,732 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassDef.java,v 1.1.1.1.2.1 2004/07/16 23:32:30 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import com.vladium.jcd.cls.attribute.AttributeElementFactory;
+import com.vladium.jcd.cls.attribute.Attribute_info;
+import com.vladium.jcd.cls.attribute.CodeAttribute_info;
+import com.vladium.jcd.cls.attribute.InnerClassesAttribute_info;
+import com.vladium.jcd.cls.constant.CONSTANT_Class_info;
+import com.vladium.jcd.cls.constant.CONSTANT_Fieldref_info;
+import com.vladium.jcd.cls.constant.CONSTANT_NameAndType_info;
+import com.vladium.jcd.cls.constant.CONSTANT_String_info;
+import com.vladium.jcd.cls.constant.CONSTANT_Utf8_info;
+import com.vladium.jcd.compiler.IClassFormatOutput;
+import com.vladium.jcd.lib.Types;
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.ByteArrayOStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This class represents the abstract syntax table (AST) that {@link com.vladium.jcd.parser.ClassDefParser}
+ * produces from bytecode. Most elements are either settable or extendible.
+ * This class also implements {@link com.vladium.jcd.compiler.IClassFormatOutput}
+ * and works with {@link com.vladium.jcd.compiler.ClassWriter} to produce
+ * bytecode without an external compiler.<P>
+ * 
+ * MT-safety: this class and all interfaces used by it are not safe for
+ * access from multiple concurrent threads. 
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class ClassDef implements Cloneable, IAccessFlags, IClassFormatOutput
+{
+    // public: ................................................................
+
+
+    public ClassDef ()
+    {
+        m_version = new int [2];
+
+        m_constants = ElementFactory.newConstantCollection (-1);
+        m_interfaces = ElementFactory.newInterfaceCollection (-1);
+        m_fields = ElementFactory.newFieldCollection (-1);
+        m_methods = ElementFactory.newMethodCollection (-1);
+        m_attributes = ElementFactory.newAttributeCollection (-1);
+    }
+    
+    // Visitor:
+    
+    public void accept (final IClassDefVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+
+
+    public long getMagic ()
+    {
+        return m_magic;
+    }
+    
+    public void setMagic (final long magic)
+    {
+        m_magic = magic;
+    }
+    
+    
+    public int [] getVersion ()
+    {
+        return m_version;
+    }
+    
+    public void setVersion (final int [] version)
+    {
+        m_version [0] = version [0];
+        m_version [1] = version [1];
+    }
+    
+    public final void setDeclaredSUID (final long suid)
+    {
+        m_declaredSUID = suid;
+    }
+    
+
+    public int getThisClassIndex ()
+    {
+        return m_this_class_index;
+    }
+    
+    public void setThisClassIndex (final int this_class_index)
+    {
+        m_this_class_index = this_class_index;
+    }
+    
+    public CONSTANT_Class_info getThisClass ()
+    {
+        return (CONSTANT_Class_info) m_constants.get (m_this_class_index);
+    }
+    
+    public CONSTANT_Class_info getSuperClass ()
+    {
+        return (CONSTANT_Class_info) m_constants.get (m_super_class_index);
+    }
+    
+    public String getName ()
+    {
+        return getThisClass ().getName (this);
+    }
+
+
+    public int getSuperClassIndex ()
+    {
+        return m_super_class_index;
+    }
+    
+    public void setSuperClassIndex (final int super_class_index)
+    {
+        m_super_class_index = super_class_index;
+    }
+    
+    // IAccessFlags:
+
+    public final int getAccessFlags ()
+    {
+        return m_access_flags;
+    }
+
+    public final void setAccessFlags (final int flags)
+    {
+        m_access_flags = flags;
+    }
+    
+    public boolean isInterface ()
+    {
+        return (m_access_flags & ACC_INTERFACE) != 0;
+    }
+    
+    public boolean isSynthetic ()
+    {
+        return m_attributes.hasSynthetic ();
+    }
+    
+    public boolean isNested (final int [] nestedAccessFlags)
+    {
+        final InnerClassesAttribute_info innerClassesAttribute = m_attributes.getInnerClassesAttribute ();
+        
+        if (innerClassesAttribute == null)
+            return false;
+        else
+            return innerClassesAttribute.makesClassNested (m_this_class_index, nestedAccessFlags);
+    }
+    
+    // methods for getting various nested tables:
+    
+    public IConstantCollection getConstants ()
+    {
+        return m_constants;
+    }
+    
+    public IInterfaceCollection getInterfaces ()
+    {
+        return m_interfaces;
+    }
+    
+    public IFieldCollection getFields ()
+    {
+        return m_fields;
+    }
+    
+    public IMethodCollection getMethods ()
+    {
+        return m_methods;
+    }
+    
+    public IAttributeCollection getAttributes ()
+    {
+        return m_attributes;
+    }
+    
+    public int [] getFields (final String name)
+    {
+        return m_fields.get (this, name);
+    }
+    
+    public int [] getMethods (final String name)
+    {
+        return m_methods.get (this, name);
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final ClassDef _clone = (ClassDef) super.clone ();
+            
+            // do deep copy:
+            _clone.m_version = (int []) m_version.clone ();
+            _clone.m_constants = (IConstantCollection) m_constants.clone ();
+            _clone.m_interfaces = (IInterfaceCollection) m_interfaces.clone ();
+            _clone.m_fields = (IFieldCollection) m_fields.clone ();
+            _clone.m_methods = (IMethodCollection) m_methods.clone ();
+            _clone.m_attributes = (IAttributeCollection) m_attributes.clone ();
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }
+    }
+    
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        if (out == null) throw new IllegalArgumentException ("null input: out");
+        
+        out.writeU4 (m_magic);
+        
+        out.writeU2 (m_version [1]);
+        out.writeU2 (m_version [0]);
+        
+        m_constants.writeInClassFormat (out);
+        
+        out.writeU2 (m_access_flags);
+        
+        out.writeU2 (m_this_class_index);
+        out.writeU2 (m_super_class_index);
+        
+        m_interfaces.writeInClassFormat (out);
+        m_fields.writeInClassFormat (out);
+        m_methods.writeInClassFormat (out);
+        m_attributes.writeInClassFormat (out);
+    }
+    
+    public final long getDeclaredSUID ()
+    {
+        return m_declaredSUID;
+    }
+    
+    /**
+     * This follows the spec at http://java.sun.com/j2se/1.4.1/docs/guide/serialization/spec/class.doc6.html#4100
+     * as well as undocumented hacks used by Sun's 1.4.2 J2SDK
+     */
+    public final long computeSUID (final boolean skipCLINIT)
+    {
+        long result = m_declaredSUID;
+        if (result != 0L)
+            return result;
+        else
+        {
+            try
+            {
+                final ByteArrayOStream bout = new ByteArrayOStream (1024); // TODO: reuse these 
+                final DataOutputStream dout = new DataOutputStream (bout);
+                
+                // (1) The class name written using UTF encoding: 
+
+                dout.writeUTF (Types.vmNameToJavaName (getName ())); // [in Java format]
+                
+                // (2) The class modifiers written as a 32-bit integer:
+                
+                // ACC_STATIC is never written for nested classes/interfaces;
+                // however, ACC_SUPER must be ignored:
+                {
+                    // this is tricky: for static/non-static nested classes that
+                    // were declared protected in the source the usual access flags
+                    // will have ACC_PUBLIC set; the only way to achieve J2SDK
+                    // compatibility is to recover the source access flags
+                    // from the InnerClasses attribute:
+                    
+                    final int [] nestedAccessFlags = new int [1];
+                    
+                    final int modifiers = (isNested (nestedAccessFlags)
+                            ? nestedAccessFlags [0]
+                            : getAccessFlags ())
+                        & (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT);
+
+                    // if/when emma decides to instrument interfaces for <clinit>
+                    // coverage, compensate for javac bug in which ABSTRACT bit
+                    // was set for an interface only if the interface declared methods
+                    // [Sun's J2SDK]:
+                    
+                    dout.writeInt (modifiers);
+                }
+                
+                // not doing another J2SDK compensation for arrays, because
+                // we never load/instrument those 
+                
+                // (3) The name of each interface sorted by name written using UTF encoding
+                {
+                    final IInterfaceCollection interfaces = getInterfaces ();
+                    final String [] ifcs = new String [interfaces.size ()];
+                    
+                    final int iLimit = ifcs.length;
+                    for (int i = 0; i < iLimit; ++ i)
+                    {
+                        // [in Java format]
+                        ifcs [i] = Types.vmNameToJavaName (((CONSTANT_Class_info) m_constants.get (interfaces.get (i))).getName (this));
+                    }
+                    
+                    Arrays.sort (ifcs);
+                    for (int i = 0; i < iLimit; ++ i)
+                    {
+                        dout.writeUTF (ifcs [i]);
+                    }
+                }
+                
+                // (4) For each field of the class sorted by field name (except
+                // private static and private transient fields):
+                //      a. The name of the field in UTF encoding. 
+                //      b. The modifiers of the field written as a 32-bit integer. 
+                //      c. The descriptor of the field in UTF encoding 
+                {
+                    final IFieldCollection fields = getFields ();
+                    final FieldDescriptor [] fds = new FieldDescriptor [fields.size ()];
+                    
+                    int fcount = 0;
+                    for (int f = 0, fLimit = fds.length; f < fLimit; ++ f)
+                    {
+                        final Field_info field = fields.get (f);
+                        final int modifiers = field.getAccessFlags ();
+                        
+                        if (((modifiers & ACC_PRIVATE) == 0) ||
+                            ((modifiers & (ACC_STATIC | ACC_TRANSIENT)) == 0))
+                            fds [fcount ++] = new FieldDescriptor (field.getName (this), modifiers, field.getDescriptor (this));
+                    }
+                    
+                    if (fcount > 0)
+                    {
+                        Arrays.sort (fds, 0, fcount);
+                        for (int i = 0; i < fcount; ++ i)
+                        {
+                            final FieldDescriptor fd = fds [i];
+                            
+                            dout.writeUTF (fd.m_name);
+                            dout.writeInt (fd.m_modifiers);
+                            dout.writeUTF (fd.m_descriptor);
+                        }
+                    }
+                }
+                
+                // (5) If a class initializer exists, write out the following: 
+                //      a. The name of the method, <clinit>, in UTF encoding. 
+                //      b. The modifier of the method, ACC_STATIC, written as a 32-bit integer. 
+                //      c. The descriptor of the method, ()V, in UTF encoding. 
+                // (6) For each non-private constructor sorted by method name and signature: 
+                //      a. The name of the method, <init>, in UTF encoding. 
+                //      b. The modifiers of the method written as a 32-bit integer. 
+                //      c. The descriptor of the method in UTF encoding. 
+                // (7) For each non-private method sorted by method name and signature: 
+                //      a. The name of the method in UTF encoding. 
+                //      b. The modifiers of the method written as a 32-bit integer. 
+                //      c. The descriptor of the method in UTF encoding.
+                
+                // note: although this is not documented, J2SDK code uses '.''s as
+                // descriptor separators (this is done for methods only, not for fields)
+                {
+                    final IMethodCollection methods = getMethods ();
+                    
+                    boolean hasCLINIT = false;
+                    final ConstructorDescriptor [] cds = new ConstructorDescriptor [methods.size ()];
+                    final MethodDescriptor [] mds = new MethodDescriptor [cds.length];
+                    
+                    int ccount = 0, mcount = 0;
+                    
+                    for (int i = 0, iLimit = cds.length; i < iLimit; ++ i)
+                    {
+                        final Method_info method = methods.get (i);
+                        
+                        final String name = method.getName (this);
+                        
+                        if (! hasCLINIT && IClassDefConstants.CLINIT_NAME.equals (name))
+                        {
+                            hasCLINIT  = true;
+                            continue;
+                        }
+                        else
+                        {
+                            final int modifiers = method.getAccessFlags ();
+                            if ((modifiers & ACC_PRIVATE) == 0)
+                            {
+                                if (IClassDefConstants.INIT_NAME.equals (name))
+                                    cds [ccount ++] = new ConstructorDescriptor (modifiers, method.getDescriptor (this));
+                                else
+                                    mds [mcount ++] = new MethodDescriptor (name, modifiers, method.getDescriptor (this));
+                            }
+                        }
+                    }
+                    
+                    if (hasCLINIT && ! skipCLINIT)
+                    {
+                        dout.writeUTF (IClassDefConstants.CLINIT_NAME);
+                        dout.writeInt (ACC_STATIC);
+                        dout.writeUTF (IClassDefConstants.CLINIT_DESCRIPTOR);
+                    }
+                    
+                    if (ccount > 0)
+                    {
+                        Arrays.sort (cds, 0, ccount);
+                        
+                        for (int i = 0; i < ccount; ++ i)
+                        {
+                            final ConstructorDescriptor cd = cds [i];
+                        
+                            dout.writeUTF (IClassDefConstants.INIT_NAME);
+                            dout.writeInt (cd.m_modifiers);
+                            dout.writeUTF (cd.m_descriptor.replace ('/', '.'));
+                        }
+                    }
+                    
+                    if (mcount > 0)
+                    {
+                        Arrays.sort (mds, 0, mcount);
+                        
+                        for (int i = 0; i < mcount; ++ i)
+                        {
+                            final MethodDescriptor md = mds [i];
+                        
+                            dout.writeUTF (md.m_name);
+                            dout.writeInt (md.m_modifiers);
+                            dout.writeUTF (md.m_descriptor.replace ('/', '.'));
+                        }
+                    }
+                }
+        
+                dout.flush();
+                
+                
+                if (DEBUG_SUID)
+                {
+                    byte [] dump = bout.copyByteArray ();
+                    for (int x = 0; x < dump.length; ++ x)
+                    {
+                        System.out.println ("DUMP[" + x + "] = " + dump [x] + "\t" + (char) dump[x]);
+                    }
+                }
+                
+                final MessageDigest md = MessageDigest.getInstance ("SHA");
+                
+                md.update (bout.getByteArray (), 0, bout.size ());
+                final byte [] hash = md.digest ();
+
+                if (DEBUG_SUID)
+                {                    
+                    for (int x = 0; x < hash.length; ++ x)
+                    {
+                        System.out.println ("HASH[" + x + "] = " + hash [x]);
+                    }
+                }
+                
+//                    final int hash0 = hash [0];
+//                    final int hash1 = hash [1];
+//                    result = ((hash0 >>> 24) & 0xFF) | ((hash0 >>> 16) & 0xFF) << 8 | ((hash0 >>> 8) & 0xFF) << 16 | ((hash0 >>> 0) & 0xFF) << 24 |
+//                             ((hash1 >>> 24) & 0xFF) << 32 | ((hash1 >>> 16) & 0xFF) << 40 | ((hash1 >>> 8) & 0xFF) << 48 | ((hash1 >>> 0) & 0xFF) << 56;
+
+                for (int i = Math.min (hash.length, 8) - 1; i >= 0; -- i)
+                {
+                    result = (result << 8) | (hash [i] & 0xFF);
+                }
+                
+                return result;
+            }
+            catch (IOException ioe)
+            {
+                throw new Error (ioe.getMessage ());
+            }
+            catch (NoSuchAlgorithmException nsae)
+            {
+                throw new SecurityException (nsae.getMessage());
+            }
+        }
+    }
+    
+    
+    public int addCONSTANT_Utf8 (final String value, final boolean keepUnique)
+    {
+        if (keepUnique)
+        {
+            final int existing = m_constants.findCONSTANT_Utf8 (value);
+            if (existing > 0)
+            {
+                return existing;
+            }
+                
+            // [else fall through]
+        }
+
+        return m_constants.add (new CONSTANT_Utf8_info (value));
+    }
+    
+    public int addStringConstant (final String value)
+    {
+        final int value_index = addCONSTANT_Utf8 (value, true);
+        
+        // TODO: const uniqueness
+        return m_constants.add (new CONSTANT_String_info (value_index));
+    }
+    
+    public int addNameType (final String name, final String typeDescriptor)
+    {
+        final int name_index = addCONSTANT_Utf8 (name, true);
+        final int descriptor_index = addCONSTANT_Utf8 (typeDescriptor, true);
+        
+        return m_constants.add (new CONSTANT_NameAndType_info (name_index, descriptor_index));
+    }
+
+
+    public int addClassref (final String classJVMName)
+    {
+        final int name_index = addCONSTANT_Utf8 (classJVMName, true);
+        // TODO: this should do uniqueness checking:
+        
+        return m_constants.add (new CONSTANT_Class_info (name_index));
+    }
+
+    
+    /**
+     * Adds a new declared field to this class [with no attributes]
+     */
+    public int addField (final String name, final String descriptor, final int access_flags)
+    {
+        // TODO: support Fields with initializer attributes?
+        // TODO: no "already exists" check done here
+        
+        final int name_index = addCONSTANT_Utf8 (name, true);
+        final int descriptor_index = addCONSTANT_Utf8 (descriptor, true);
+        
+        final Field_info field = new Field_info (access_flags, name_index, descriptor_index,
+            ElementFactory.newAttributeCollection (0));
+        
+        return m_fields.add (field);
+    }
+    
+    /**
+     * Adds a new declared field to this class [with given attributes]
+     */
+    public int addField (final String name, final String descriptor, final int access_flags,
+                         final IAttributeCollection attributes)
+    {
+        // TODO: support Fields with initializer attributes?
+        // TODO: no "already exists" check done here
+        
+        final int name_index = addCONSTANT_Utf8 (name, true);
+        final int descriptor_index = addCONSTANT_Utf8 (descriptor, true);
+        
+        final Field_info field = new Field_info (access_flags, name_index, descriptor_index, attributes);
+        
+        return m_fields.add (field);
+    }
+
+    
+    // TODO: rework this API
+    
+    public Method_info newEmptyMethod (final String name, final String descriptor, final int access_flags)
+    {
+        // TODO: flag for making synthetic etc
+        final int attribute_name_index = addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true);
+        final int name_index = addCONSTANT_Utf8 (name, true);
+        final int descriptor_index = addCONSTANT_Utf8 (descriptor, true);
+        
+        final IAttributeCollection attributes = ElementFactory.newAttributeCollection (0);
+        final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index, 0, 0,
+            CodeAttribute_info.EMPTY_BYTE_ARRAY,
+            AttributeElementFactory.newExceptionHandlerTable (0),
+            ElementFactory.newAttributeCollection (0));
+            
+        attributes.add (code);
+        
+        final Method_info method = new Method_info (access_flags, name_index, descriptor_index, attributes);
+        
+        return method;
+    }
+    
+    public int addMethod (final Method_info method)
+    {
+        return m_methods.add (method);
+    }
+    
+    /**
+     * Adds a reference to a field declared by this class.
+     * 
+     * @return constant pool index of the reference
+     */
+    public int addFieldref (final Field_info field)
+    {
+        // TODO: keepUnique flag
+        
+        final CONSTANT_NameAndType_info nametype = new CONSTANT_NameAndType_info (field.m_name_index, field.m_descriptor_index);
+        final int nametype_index = m_constants.add (nametype); // TODO: unique logic
+        
+        return m_constants.add (new CONSTANT_Fieldref_info (m_this_class_index, nametype_index));
+    }
+    
+    /**
+     * Adds a reference to a field declared by this class.
+     * 
+     * @return constant pool index of the reference
+     */
+    public int addFieldref (final int offset)
+    {
+        // TODO: keepUnique flag
+        
+        final Field_info field = m_fields.get (offset); 
+        
+        final CONSTANT_NameAndType_info nametype = new CONSTANT_NameAndType_info (field.m_name_index, field.m_descriptor_index);
+        final int nametype_index = m_constants.add (nametype); // TODO: unique logic
+        
+        return m_constants.add (new CONSTANT_Fieldref_info (m_this_class_index, nametype_index));
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+    
+    private static final class FieldDescriptor implements Comparable
+    {
+        // Comparable:
+        
+        public final int compareTo (final Object obj)
+        {
+            return m_name.compareTo (((FieldDescriptor) obj).m_name);
+        }
+
+        FieldDescriptor (final String name, final int modifiers, final String descriptor)
+        {
+            m_name = name;
+            m_modifiers = modifiers;
+            m_descriptor = descriptor;
+        }
+
+        
+        final String m_name;
+        final int m_modifiers;
+        final String m_descriptor; 
+        
+    } // end of nested class
+    
+    
+    private static final class ConstructorDescriptor implements Comparable
+    {
+        // Comparable:
+        
+        public final int compareTo (final Object obj)
+        {
+            return m_descriptor.compareTo (((ConstructorDescriptor) obj).m_descriptor);
+        }
+        
+        ConstructorDescriptor (final int modifiers, final String descriptor)
+        {
+            m_modifiers = modifiers;
+            m_descriptor = descriptor;
+        }
+        
+
+        final int m_modifiers;
+        final String m_descriptor; 
+        
+    } // end of nested class
+    
+    
+    private static final class MethodDescriptor implements Comparable
+    {
+        // Comparable:
+        
+        public final int compareTo (final Object obj)
+        {
+            final MethodDescriptor rhs = (MethodDescriptor) obj;
+            
+            int result = m_name.compareTo (rhs.m_name);
+            if (result == 0)
+                result = m_descriptor.compareTo (rhs.m_descriptor);
+            
+            return result;
+        }
+
+        MethodDescriptor (final String name, final int modifiers, final String descriptor)
+        {
+            m_name = name;
+            m_modifiers = modifiers;
+            m_descriptor = descriptor;
+        }
+
+        
+        final String m_name;
+        final int m_modifiers;
+        final String m_descriptor; 
+        
+    } // end of nested class   
+    
+
+    // TODO: final fields
+    
+    private long m_magic;
+    private int [] /* major, minor */ m_version;
+    private int m_access_flags;
+    
+    private int m_this_class_index, m_super_class_index;
+    
+    private IConstantCollection m_constants;
+    private IInterfaceCollection m_interfaces;
+    private IFieldCollection m_fields;
+    private IMethodCollection m_methods;
+    private IAttributeCollection m_attributes;
+    
+    private long m_declaredSUID;
+    
+    private static final boolean DEBUG_SUID = false;
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/ConstantCollection.java b/core/java12/com/vladium/jcd/cls/ConstantCollection.java
new file mode 100644
index 0000000..5831a12
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/ConstantCollection.java
@@ -0,0 +1,303 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ConstantCollection.java,v 1.1.1.1 2004/05/09 16:57:45 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.cls.constant.*;
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.ObjectIntMap;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vladimir Roubtsov
+ */
+final class ConstantCollection implements IConstantCollection
+{
+    // public: ................................................................
+
+    // IConstantCollection:
+    
+    // ACCESSORS:
+        
+    public CONSTANT_info get (final int index)
+    {
+        final Object result = m_constants.get (index - 1);
+        
+        if (result == null)
+            throw new IllegalStateException ("assertion failure: dereferencing an invalid constant pool slot " + index);
+        
+        return (CONSTANT_info) result;
+    }
+
+    public IConstantCollection.IConstantIterator iterator ()
+    {
+        return new ConstantIterator (m_constants);
+    }
+    
+    public int find (final int type, final IConstantComparator comparator)
+    {
+        if (comparator == null)
+            throw new IllegalArgumentException ("null input: comparator");
+        
+        for (int i = 0; i < m_constants.size (); ++ i)
+        {
+            final CONSTANT_info constant = (CONSTANT_info) m_constants.get (i);
+            
+            if ((constant != null) && (constant.tag () == type) && comparator.equals (constant))
+                return i /* !!! */ + 1; 
+        }
+        
+        return -1;
+    }
+    
+    public int findCONSTANT_Utf8 (final String value)
+    {
+        if (value == null)
+            throw new IllegalArgumentException ("null input: value");
+        
+        // create index lazily:
+        final ObjectIntMap index = getCONSTANT_Utf8_index ();
+        final int [] result = new int [1];
+        
+        if (index.get (value, result))
+            return result [0] /* !!! */ + 1;
+        else
+            return -1;
+    }
+    
+    public int size ()
+    {
+        return m_size;
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final ConstantCollection _clone = (ConstantCollection) super.clone ();
+            
+            // deep copy:
+            final int constants_count = m_constants.size ();
+            _clone.m_constants = new ArrayList (constants_count);
+            for (int c = 0; c < constants_count; ++ c)
+            {
+                final CONSTANT_info constant = (CONSTANT_info) m_constants.get (c);
+                _clone.m_constants.add (constant == null ? null : constant.clone ());
+            }
+            
+            // note: m_CONSTANT_Utf8_index is not cloned intentionally
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        final int constant_pool_count = m_constants.size (); // note: this is not the same as size()
+        out.writeU2 (constant_pool_count + /* !!! */1);
+        
+        final ConstantIterator i = new ConstantIterator (m_constants);
+        for (CONSTANT_info entry; (entry = i.nextConstant ()) != null; )
+        {
+            entry.writeInClassFormat (out);
+        }
+    }
+    
+    // Visitor:
+    
+    public void accept (final IClassDefVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    
+    // MUTATORS:
+    
+    public CONSTANT_info set (final int index, final CONSTANT_info constant)
+    {
+        final int zindex = index - 1;
+        final CONSTANT_info result = (CONSTANT_info) m_constants.get (zindex);
+        
+        if (result == null)
+            throw new IllegalStateException ("assertion failure: dereferencing an invalid constant pool slot " + index);
+            
+        if (result.width () != constant.width ())
+            throw new IllegalArgumentException ("assertion failure: can't set entry of type [" + result.getClass ().getName () + "] to an entry of type [" + result.getClass ().getName () + "] at pool slot " + index);
+        
+        m_constants.set (zindex, constant);
+        
+        // update the string index if it is in use:
+        if (m_CONSTANT_Utf8_index != null)
+        {
+            // remove the old index value if it exists and is equal to 'index':
+            if (result instanceof CONSTANT_Utf8_info)
+            {
+                final String mapKey = ((CONSTANT_Utf8_info) result).m_value;
+                final int [] out = new int [1];
+        
+                if (m_CONSTANT_Utf8_index.get (mapKey, out) && (out [0] == zindex))
+                    m_CONSTANT_Utf8_index.remove (mapKey);
+            }
+            
+            // add new index value if necessary:
+            if (constant instanceof CONSTANT_Utf8_info)
+                m_CONSTANT_Utf8_index.put (((CONSTANT_Utf8_info) constant).m_value, zindex);
+        }
+        
+        return result;
+    }
+
+    public int add (final CONSTANT_info constant)
+    {
+        m_constants.add (constant);
+        ++ m_size; 
+        final int result = m_constants.size ();
+        
+        for (int width = 1; width < constant.width (); ++ width)
+        {
+            ++ m_size;
+            m_constants.add (null); // insert padding empty slots            
+        }
+        
+        // update the string index if it is in use:
+        if ((m_CONSTANT_Utf8_index != null) && (constant instanceof CONSTANT_Utf8_info))
+            m_CONSTANT_Utf8_index.put (((CONSTANT_Utf8_info) constant).m_value, result /* !!! */ - 1);
+        
+        return result;
+    }    
+        
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    ConstantCollection (final int capacity)
+    {
+        m_constants = capacity < 0 ? new ArrayList () : new ArrayList (capacity);
+    }
+
+    // private: ...............................................................
+
+    
+    private static final class ConstantIterator implements IConstantCollection.IConstantIterator
+    {
+        ConstantIterator (final List/* CONSTANT_info */ constants)
+        {
+            m_constants = constants;
+            m_next_index = 1;
+            shift ();
+        }
+        
+        
+        public int nextIndex ()
+        {
+            final int result = m_index;
+            shift ();
+            
+            return result;
+        }
+        
+        public CONSTANT_info nextConstant ()
+        {
+            final int nextIndex = nextIndex ();
+            if (nextIndex < 0)
+                return null;
+            else
+                return (CONSTANT_info) m_constants.get (nextIndex - 1);
+        }
+        
+        public CONSTANT_info set (final CONSTANT_info constant)
+        {
+            final int zindex = m_prev_index - 1;
+            final CONSTANT_info result = (CONSTANT_info) m_constants.get (zindex);
+        
+            if (result == null) // this should never happen with iterators
+                throw new IllegalStateException ("assertion failure: dereferencing an invalid constant pool slot " + m_prev_index);
+                
+            if (result.width () != constant.width ())
+                throw new IllegalArgumentException ("assertion failure: can't set entry of type [" + result.getClass ().getName () + "] to an entry of type [" + result.getClass ().getName () + "] at pool slot " + m_prev_index);
+            
+            m_constants.set (zindex, constant);
+            
+            return result;
+        }
+        
+        
+        private void shift ()
+        {
+            m_prev_index = m_index;
+            m_index = m_next_index;
+            
+            if (m_index > 0)
+            {
+                try
+                {
+                    final CONSTANT_info entry = (CONSTANT_info) m_constants.get (m_index - 1);
+
+                    m_next_index += entry.width ();
+                    if (m_next_index > m_constants.size ()) m_next_index = -1;
+                }
+                catch (IndexOutOfBoundsException ioobe) // empty collection edge case
+                {
+                    m_index = m_next_index = -1;
+                }
+            }
+        }
+        
+        
+        private int m_index, m_prev_index, m_next_index;
+        private List/* CONSTANT_info */ m_constants;
+        
+    } // end of nested class
+    
+    
+    private ObjectIntMap getCONSTANT_Utf8_index ()
+    {
+        if (m_CONSTANT_Utf8_index == null)
+        {
+            final ObjectIntMap index = new ObjectIntMap (m_size);
+            
+            for (int i = 0; i < m_constants.size (); ++ i)
+            {
+                final CONSTANT_info constant = (CONSTANT_info) m_constants.get (i);
+                
+                if ((constant != null) && (constant.tag () == CONSTANT_Utf8_info.TAG))
+                {
+                    // it's ok to always put: the later indices will simply override the earlier ones
+                    index.put (((CONSTANT_Utf8_info) constant).m_value, i); // note: unadjusted index saved here
+                }
+            }
+            
+            m_CONSTANT_Utf8_index = index;
+        }
+        
+        return m_CONSTANT_Utf8_index;
+    }
+
+
+    private List/* CONSTANT_info */ m_constants; // never null
+    private int m_size;
+    private transient ObjectIntMap /* String(CONSTANT_Utf value) -> int(index) */ m_CONSTANT_Utf8_index;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/ElementFactory.java b/core/java12/com/vladium/jcd/cls/ElementFactory.java
new file mode 100644
index 0000000..2922837
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/ElementFactory.java
@@ -0,0 +1,53 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ElementFactory.java,v 1.1.1.1 2004/05/09 16:57:45 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class ElementFactory
+{
+    // public: ................................................................
+    
+    
+    public static IAttributeCollection newAttributeCollection (final int capacity)
+    {
+        return new AttributeCollection (capacity);
+    }
+
+    public static IConstantCollection newConstantCollection (final int capacity)
+    {
+        return new ConstantCollection (capacity);
+    }
+
+    public static IFieldCollection newFieldCollection (final int capacity)
+    {
+        return new FieldCollection (capacity);
+    }
+
+    public static IInterfaceCollection newInterfaceCollection (final int capacity)
+    {
+        return new InterfaceCollection (capacity);
+    }
+
+    public static IMethodCollection newMethodCollection (final int capacity)
+    {
+        return new MethodCollection (capacity);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/FieldCollection.java b/core/java12/com/vladium/jcd/cls/FieldCollection.java
new file mode 100644
index 0000000..f0a061e
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/FieldCollection.java
@@ -0,0 +1,137 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: FieldCollection.java,v 1.1.1.1 2004/05/09 16:57:45 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.IntVector;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+final class FieldCollection implements IFieldCollection
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+        
+    public Field_info get (final int offset)
+    {
+        return (Field_info) m_fields.get (offset);
+    }
+    
+    public int [] get (final ClassDef cls, final String name)
+    {
+        if (cls == null) throw new IllegalArgumentException  ("null input: cls");
+        
+        // TODO: hash impl [not possible without having access to the parent ClassDef]
+        
+        final int count = m_fields.size (); // use size() if class becomes non-final
+        final IntVector result = new IntVector (count);
+
+        for (int f = 0; f < count; ++ f)
+        {
+            final Field_info field = (Field_info) m_fields.get (f);
+            
+            if (field.getName (cls).equals (name))  
+                result.add (f);
+        }
+        
+        return result.values (); // IntVector optimizes for the empty case 
+    }
+    
+    public int size ()
+    {
+        return m_fields.size ();
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final FieldCollection _clone = (FieldCollection) super.clone ();
+            
+            // deep clone:
+            final int fields_count = m_fields.size (); // use size() if class becomes non-final
+            _clone.m_fields = new ArrayList (fields_count);
+            for (int f = 0; f < fields_count; ++ f)
+            {
+                _clone.m_fields.add (((Field_info) m_fields.get (f)).clone ());
+            }
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        final int fields_count = m_fields.size (); // use size() if class becomes non-final
+        out.writeU2 (fields_count);
+        
+        for (int i = 0; i < fields_count; i++)
+        {
+            get (i).writeInClassFormat (out);
+        }
+    }
+    
+    // Visitor:
+    
+    public void accept (final IClassDefVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+
+    // MUTATORS:
+
+    public int add (final Field_info field)
+    {
+        final int newoffset = m_fields.size (); // use size() if class becomes non-final
+        m_fields.add (field);
+        
+        return newoffset;
+    }
+    
+    public Field_info set (final int offset, final Field_info field)
+    {
+        return (Field_info) m_fields.set (offset, field);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    FieldCollection (final int capacity)
+    {
+        m_fields = capacity < 0 ? new ArrayList () : new ArrayList (capacity);
+    }
+
+    // private: ...............................................................
+
+    
+    private List/* Field_info */ m_fields; // never null
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/Field_info.java b/core/java12/com/vladium/jcd/cls/Field_info.java
new file mode 100644
index 0000000..22a9dcd
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/Field_info.java
@@ -0,0 +1,197 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Field_info.java,v 1.1.1.1 2004/05/09 16:57:45 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.attribute.*;
+import com.vladium.jcd.cls.constant.CONSTANT_Utf8_info;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * Each class field is described by a variable-length field_info structure. The
+ * format of this structure is
+ * <PRE>
+ *  field_info {
+ *          u2 access_flags;
+ *          u2 name_index;
+ *          u2 descriptor_index;
+ *          u2 attributes_count;
+ *          attribute_info attributes[attributes_count];
+ *  }
+ * </PRE>
+ * 
+ * The value of the access_flags item is a mask of modifiers used to describe
+ * access permission to and properties of a field.<P>
+ * 
+ * The value of the name_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure which must represent a valid Java field name stored as a simple (not
+ * fully qualified) name, that is, as a Java identifier.<P>
+ * 
+ * The value of the descriptor_index item must be a valid index into the constant
+ * pool table. The constant pool entry at that index must be a
+ * {@link CONSTANT_Utf8_info} structure which must represent a valid Java field
+ * descriptor.<P>
+ * 
+ * Each value of the attributes table must be a variable-length attribute structure.
+ * A field can have any number of attributes associated with it. The only attribute
+ * defined for the attributes table of a field_info structure at the moment
+ * is the ConstantValue attribute -- see {@link ConstantValueAttribute_info}.
+ *  
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class Field_info implements Cloneable, IAccessFlags
+{
+    // public: ................................................................
+
+    
+    public int m_name_index;
+    public int m_descriptor_index;
+    
+    
+    public Field_info (final int access_flags,
+                       final int name_index, final int descriptor_index,
+                       final IAttributeCollection attributes)
+    {
+        m_access_flags = access_flags;
+        
+        m_name_index = name_index;
+        m_descriptor_index = descriptor_index;
+        
+        m_attributes = attributes;
+    }
+
+    public Field_info (final IConstantCollection constants,
+                       final UDataInputStream bytes)
+        throws IOException
+    {
+        m_access_flags = bytes.readU2 ();
+        
+        m_name_index = bytes.readU2 ();
+        m_descriptor_index = bytes.readU2 ();
+        
+        // TODO: put this logic into AttributeCollection
+        final int attributes_count = bytes.readU2 ();
+        m_attributes = ElementFactory.newAttributeCollection (attributes_count);
+        
+        for (int i = 0; i < attributes_count; i++)
+        {
+            final Attribute_info attribute_info = Attribute_info.new_Attribute_info (constants, bytes);
+            if (DEBUG) System.out.println ("\t[" + i + "] attribute: " + attribute_info);
+            
+            m_attributes.add (attribute_info);
+        }
+    }
+    
+    /**
+     * Returns the field name within the context of 'cls' class definition.
+     * 
+     * @param cls class that contains this field
+     * @return field name
+     */
+    public String getName (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_name_index)).m_value;
+    }
+    
+    /**
+     * Returns the descriptor string for this field within the context of 'cls'
+     * class definition.
+     * 
+     * @param cls class that contains this field
+     * @return field typename descriptor
+     */
+    public String getDescriptor (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_descriptor_index)).m_value;
+    }
+    
+    public boolean isSynthetic ()
+    {
+        return m_attributes.hasSynthetic ();
+    }
+    
+    // IAccessFlags:
+        
+    public final void setAccessFlags (final int flags)
+    {
+        m_access_flags = flags;
+    }
+
+    public final int getAccessFlags ()
+    {
+        return m_access_flags;
+    }
+    
+    public IAttributeCollection getAttributes ()
+    {
+        return m_attributes;
+    }
+    
+    
+    public String toString ()
+    {
+        return "field_info: [modifiers: 0x" + Integer.toHexString(m_access_flags) + ", name_index = " + m_name_index + ", descriptor_index = " + m_descriptor_index + ']';
+    }
+    
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final Field_info _clone = (Field_info) super.clone ();
+            
+            // do deep copy:
+            _clone.m_attributes = (IAttributeCollection) m_attributes.clone ();
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }
+    }
+    
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {    
+        out.writeU2 (m_access_flags);
+        
+        out.writeU2 (m_name_index);
+        out.writeU2 (m_descriptor_index);
+        
+        m_attributes.writeInClassFormat (out);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+    
+    private int m_access_flags;
+    private IAttributeCollection m_attributes; // never null
+
+    
+    private static final boolean DEBUG = false;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IAccessFlags.java b/core/java12/com/vladium/jcd/cls/IAccessFlags.java
new file mode 100644
index 0000000..d6e73c0
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IAccessFlags.java
@@ -0,0 +1,68 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IAccessFlags.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IAccessFlags
+{
+    // public: ................................................................
+
+    static final int ACC_PUBLIC         = 0x0001;
+    static final int ACC_PRIVATE        = 0x0002;
+    static final int ACC_PROTECTED      = 0x0004;
+    static final int ACC_STATIC         = 0x0008;
+    static final int ACC_FINAL          = 0x0010;
+    static final int ACC_SYNCHRONIZED   = 0x0020;
+    static final int ACC_SUPER          = 0x0020; // same bit value as ACC_SYNCHRONIZED
+    static final int ACC_VOLATILE       = 0x0040;
+    static final int ACC_BRIDGE         = 0x0040; // same bit value as ACC_VOLATILE
+    static final int ACC_TRANSIENT      = 0x0080;
+    static final int ACC_NATIVE         = 0x0100;
+    static final int ACC_INTERFACE      = 0x0200;
+    static final int ACC_ABSTRACT       = 0x0400;
+    
+    static final int ALL_ACC [] = new int []
+    {
+        ACC_PUBLIC,
+        ACC_PRIVATE,
+        ACC_PROTECTED,
+        ACC_STATIC,
+        ACC_FINAL,
+        ACC_SYNCHRONIZED,     
+        ACC_VOLATILE,
+        ACC_TRANSIENT,
+        ACC_NATIVE,
+        ACC_INTERFACE,
+        ACC_ABSTRACT,
+    };
+    
+    static final String ALL_ACC_NAMES [] = new String []
+    {
+        "public",
+        "private",
+        "protected",
+        "static",
+        "final",
+        "synchronized",     
+        "volatile",
+        "transient",
+        "native",
+        "interface",
+        "abstract",
+    };
+    
+    void setAccessFlags (int flags);
+    int getAccessFlags ();
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IAttributeCollection.java b/core/java12/com/vladium/jcd/cls/IAttributeCollection.java
new file mode 100644
index 0000000..4d79328
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IAttributeCollection.java
@@ -0,0 +1,98 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IAttributeCollection.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import com.vladium.jcd.cls.attribute.*;
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * An abstraction of the 'attributes' component of .class format. The contents
+ * are {@link Attribute_info} structures. The order in which they appear is
+ * unspecified.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IAttributeCollection extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+    
+    /**
+     * Returns the attribute descriptor at a given offset.
+     * 
+     * @param offset attribute offset [must be in [0, size()) range; input not checked]
+     * @return Attribute_info descriptor [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Attribute_info get (int offset);
+    
+    boolean hasSynthetic ();
+    boolean hasBridge ();
+    InnerClassesAttribute_info getInnerClassesAttribute ();
+    
+    /**
+     * Returns the number of attributes in this collection [can be 0].
+     */
+    int size ();
+    
+    /**
+     * Returns the total length of this collection when converted to
+     * .class format [including 2 count bytes]
+     */
+    long length ();
+    
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+    
+    // Visitor:
+    void accept (IClassDefVisitor visitor, Object ctx);
+    
+    
+    // MUTATORS:
+    
+    /**
+     * Adds a new Attribute_info descriptor to this collection. No duplicate
+     * checks are made. It is the responsibility of the caller to ensure
+     * that all data referenced in 'attribute' will eventually appear in the
+     * constant pool.
+     * 
+     * @param attribute new attribute descriptor [may not be null]
+     */
+    int add (Attribute_info attribute);
+    
+    /**
+     * Replaces the Attribute_info descriptor at a given offset. No duplicate
+     * checks are made. It is the responsibility of the caller to ensure that
+     * all data referenced in 'attribute' will eventually appear in the constant
+     * pool.
+     * 
+     * @param offset attribute offset [must be in [0, size()) range; input not checked]
+     * @param attribute new attribute descriptor [may not be null]
+     * @return previous attribute descriptor at this offset [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Attribute_info set (int offset, Attribute_info attribute);
+    
+    /**
+     * Removes the Attribute_info descriptor at a given offset.
+     * 
+     * @param offset attribute offset [must be in [0, size()) range; input not checked]
+     * @return current attribute descriptor at this offset [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Attribute_info remove (int offset);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IClassDefConstants.java b/core/java12/com/vladium/jcd/cls/IClassDefConstants.java
new file mode 100644
index 0000000..0b81a94
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IClassDefConstants.java
@@ -0,0 +1,25 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IClassDefConstants.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IClassDefConstants
+{
+    // public: ................................................................
+    
+    String INIT_NAME = "<init>";
+    String CLINIT_NAME = "<clinit>";
+    String CLINIT_DESCRIPTOR = "()V";
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/jcd/cls/IClassDefVisitor.java b/core/java12/com/vladium/jcd/cls/IClassDefVisitor.java
new file mode 100644
index 0000000..e49a6d4
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IClassDefVisitor.java
@@ -0,0 +1,28 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IClassDefVisitor.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public interface IClassDefVisitor
+{
+    // public: ................................................................
+    
+    Object visit (ClassDef cls, Object ctx);
+    
+    Object visit (IConstantCollection constants, Object ctx);
+    Object visit (IInterfaceCollection interfaces, Object ctx);
+    Object visit (IFieldCollection fields, Object ctx);
+    Object visit (IMethodCollection methods, Object ctx);
+    Object visit (IAttributeCollection attributes, Object ctx);
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IConstantCollection.java b/core/java12/com/vladium/jcd/cls/IConstantCollection.java
new file mode 100644
index 0000000..3a7931a
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IConstantCollection.java
@@ -0,0 +1,175 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IConstantCollection.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import com.vladium.jcd.cls.constant.*;
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * An abstraction of constant pool in .class format. This interface disallows
+ * any pool mutation that invalidates already existing pool indices. 
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IConstantCollection extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    /**
+     * A custom fail-fast iterator class returned by {@link IConstantCollection#iterator()}.
+     * It allows iterating over all entries in a way that steps over all
+     * 'invalid' inner slots (extra slots consumed by CONSTANT_Long and
+     * CONSTANT_Double entries).
+     */
+    interface IConstantIterator
+    {
+        /**
+         * Returns the next entry slot index.
+         * 
+         * @return int next valid slot index [always positive for a valid slot;
+         * -1 when the enumeration is exhausted]
+         */
+        public int nextIndex ();
+        
+        /**
+         * Returns the next entry. This is a convenience method for doing
+         * get(nextIndex()) and avoiding index bound violation exceptions.
+         * 
+         * @return CONSTANT_info next valid entry [null when the enumeration is
+         * exhausted]
+         */
+        public CONSTANT_info nextConstant ();
+        
+        /**
+         * A convenience method that is equivalent to {@link IConstantCollection#set}
+         * and replaces the entry that was visited last without invalidating
+         * the iterator. 
+         */
+        CONSTANT_info set (CONSTANT_info constant);
+        
+    } // end of nested interface
+    
+    
+    /**
+     * A simple interface to express custom semantics of constant equality.
+     * 
+     * @see IConstantCollection#find(int, IConstantComparator)
+     */
+    interface IConstantComparator
+    {
+        boolean equals (CONSTANT_info constant);
+        
+    } // end of nested interface
+    
+    
+    // ACCESSORS:
+    
+    /**
+     * Returns a CONSTANT_info at a given pool index. Note that 'index' is
+     * 1-based [the way an index would be embedded in bytecode instructions].
+     * Note that because CONSTANT_Long and CONSTANT_Double entries occupy
+     * two consequitive index slots certain index values inside the valid range
+     * can be invalid; use {@link #iterator()} to iterate only over valid entries
+     * in a transparent fashion. 
+     * 
+     * @param index constant pool index [must be in [1, size()] range]
+     * @return CONSTANT_info constant pool entry at this index [never null]
+     * 
+     * @throws IllegalStateException if an attempt is made to reference
+     * an invalid slot index
+     * @throws IndexOutOfBoundsException if an attempt is made to reference
+     * a slot outside of the valid range 
+     */
+    CONSTANT_info get (int index);
+    
+    /**
+     * Returns a fail-fast iterator over all valid entries in the pool. The
+     * resulting object would be invalidated by simultaneous mutation to the
+     * underlying collection pool.
+     * 
+     * @return IConstantIterator iterator over all entries in the collection [never null]
+     */
+    IConstantIterator iterator ();
+    
+    /**
+     * Searches the pool for a matching constant of given type with equality
+     * semantics expressed by 'comparator'. This method guarantees that
+     * when comparator.equals(c) is called c.type() is 'type'. The cost is
+     * O(pool size). When multiple matches exist, the location of the first one
+     * found will be returned (chosen in some indeterministic way).
+     *  
+     * @param type type of constants to filter by [not validated]
+     * @param comparator [may not be null]
+     * @return index of the first found entry [-1 if not found] 
+     * 
+     * @throws IllegalArgumentException if 'comparator' is null
+     */
+    int find (int type, IConstantComparator comparator);
+    
+    /**
+     * Convenience method that can lookup CONSTANT_Utf8 entries in O(1) time
+     * on average. Note that .class format does not guarantee that all such
+     * entries are not duplicated in the pool. When multiple matches exist, the
+     * location of the first one found will be returned (chosen in some
+     * indeterministic way).
+     * 
+     * @param value string value on which to match [may not be null]
+     * @return index of the first found entry [-1 if not found]
+     * 
+     * @throws IllegalArgumentException if 'value' is null
+     */
+    int findCONSTANT_Utf8 (String value);
+    
+    /**
+     * Returns the number of CONSTANT_info entries in this collection. 
+     * 
+     * @return the number of constants in this pool [can be 0]
+     */
+    int size ();
+        
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+    
+    // Visitor:
+    void accept (IClassDefVisitor visitor, Object ctx);
+    
+
+    // MUTATORS:
+    
+    /**
+     * Appends 'constant' to the end of the collection. No duplicate checks
+     * are made.
+     * 
+     * @param constant new constant [may not be null; input unchecked]
+     * @return the pool index of the newly added entry [always positive]
+     */
+    int add (CONSTANT_info constant);
+    
+    /**
+     * Replaces an existing constant pool entry. A replacement can be made only
+     * for a constant of the same width as the constant currently occupying the
+     * slot. 
+     * 
+     * @param index constant pool index [must be in [1, size()] range]
+     * @param constant new entry to set [may not be null; input unchecked]
+     * @return CONSTANT_info previous contents at this pool index [never null]
+     * 
+     * @throws IllegalArgumentException if the new constant's width is different
+     * from the current entry's
+     * @throws IllegalStateException if an attempt is made to reference
+     * an invalid slot index [see {@link #get(int)}]
+     * @throws IndexOutOfBoundsException if an attempt is made to reference
+     * a slot outside of the valid range
+     */
+    CONSTANT_info set (int index, CONSTANT_info constant);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IFieldCollection.java b/core/java12/com/vladium/jcd/cls/IFieldCollection.java
new file mode 100644
index 0000000..7cab5ee
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IFieldCollection.java
@@ -0,0 +1,94 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IFieldCollection.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * An abstraction of the 'fields' component of .class format. The contents
+ * are {@link Field_info} structures corresponding to all fields directly
+ * declared by this class/interface. The order in which they appear is
+ * unspecified.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IFieldCollection extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+    
+    // ACCESSORS:
+    
+    /**
+     * Returns {@link Field_info} descriptor at a given offset.
+     * 
+     * @param offset field offset [must be in [0, size()) range; input not checked]
+     * @return Field_info descriptor [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Field_info get (int offset);
+    
+    /**
+     * Returns an array of offsets for fields named 'name' (empty array if no
+     * matching fields found). Note: even though Java syntax disallows for a class
+     * to have multiple fields with the same name it is possible at the bytecode
+     * level (as long as the type descriptors disambiguate).
+     * 
+     * @param cls class definition providing the constant pool against which to
+     * resolve names [may not be null]
+     * @param name field name [null or empty will result in no matches]
+     * @return array of field offsets in no particular order [never null; could be empty]
+     * 
+     * @throws IllegalArgumentException if 'cls' is null
+     */
+    int [] get (ClassDef cls, String name);
+    
+    /**
+     * Returns the number of fields in this collection [can be 0].
+     */
+    int size ();
+
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+    
+    // Visitor:
+    void accept (IClassDefVisitor visitor, Object ctx);
+
+
+    // MUTATORS:
+    
+    /**
+     * Adds a new Field_info descriptor to this collection. No duplicate
+     * checks are made. It is the responsibility of the caller to ensure
+     * that all data referenced in 'field' will eventually appear in the
+     * constant pool.
+     * 
+     * @param field new field descriptor [may not be null]
+     * @return new field's offset
+     */
+    int add (Field_info field);
+    
+    /**
+     * Replaces the Field_info descriptor at a given offset. No duplicate
+     * checks are made. No field type compatibility checks are made.  It is
+     * the responsibility of the caller to ensure that all data referenced
+     * in 'field' will eventually appear in the constant pool.
+     * 
+     * @param offset field offset [must be in [0, size()) range; input not checked]
+     * @param field new field descriptor [may not be null]
+     * @return previous field descriptor at this offset [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Field_info set (int offset, Field_info field);
+        
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IInterfaceCollection.java b/core/java12/com/vladium/jcd/cls/IInterfaceCollection.java
new file mode 100644
index 0000000..732b6c0
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IInterfaceCollection.java
@@ -0,0 +1,81 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IInterfaceCollection.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * An abstraction of the 'interfaces' component of .class format. The contents
+ * are constant pool indices of {@link com.vladium.jcd.cls.constant.CONSTANT_Class_info}
+ * structures corresponding to direct superinterfaces of this class/interface.
+ * The order in which they appear is the left-to-right order of their declaration in
+ * the implements/extends clause.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IInterfaceCollection extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+    
+    /**
+     * Returns the {@link com.vladium.jcd.cls.constant.CONSTANT_Class_info}
+     * constant pool index for offset'th direct superinterface.
+     * 
+     * @param offset superinterface number [must be in [0, size()) range]
+     * @return constant pool index [always positive]  
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    int get (int offset);
+    
+    /**
+     * Returns the number of direct superinterfaces for this class/interface.
+     * 
+     * @return int number of direct superinterfaces [can be 0]
+     */
+    int size ();
+    
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+    
+    // Visitor:
+    void accept (IClassDefVisitor visitor, Object ctx);
+
+
+    // MUTATORS:
+    
+    /**
+     * Appends a new superinterface pointer to the collection. No duplicate checks are made.
+     * 
+     * @param interface_index constant pool index [must be positive; input not validated]
+     * @return offset of the new pointer [same as {@link #size()}-1 when called
+     * after this method] 
+     */
+    int add (int interface_index);
+    
+    /**
+     * Replaces superinterface pointer number 'offset' with new value 'interface_index'.
+     * No duplicate checks are made. It is the responsibility of the caller to
+     * ensure that the relevant CONSTANT_Class_info descriptor will be found
+     * in the constant pool, in the slot pointed to by 'interface_index'.
+     * 
+     * @param offset offset of the superinterface pointer to replace [must be in [0, size()) range]
+     * @param interface_index constant pool index [must be positive; input not validated]
+     * @return previous value at the given index [always positive]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    int set (int offset, int interface_index);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/IMethodCollection.java b/core/java12/com/vladium/jcd/cls/IMethodCollection.java
new file mode 100644
index 0000000..91ad3b8
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/IMethodCollection.java
@@ -0,0 +1,104 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IMethodCollection.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * An abstraction of the 'methods' component of .class format. The contents
+ * are {@link Method_info} structures corresponding to all methods directly
+ * declared by this class/interface. The order in which they appear is
+ * unspecified.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IMethodCollection extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+    
+    /**
+     * Returns {@link Method_info} descriptor at a given offset.
+     * 
+     * @param offset method offset [must be in [0, size()) range; input not checked]
+     * @return Method_info descriptor [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Method_info get (int offset);
+    
+    /**
+     * Returns an array of offsets for methods named 'name' (empty array if no
+     * matching fields found).
+     * 
+     * @param cls class definition providing the constant pool against which to
+     * resolve names [may not be null]
+     * @param name method name [null or empty will result in no matches]
+     * @return array of method offsets in no particular order [never null; could be empty]
+     * 
+     * @throws IllegalArgumentException if 'cls' is null
+     */
+    int [] get (ClassDef cls, String name);
+    
+    /**
+     * Returns the number of methods in this collection [can be 0].
+     */
+    int size ();
+
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+    
+    // Visitor:
+    void accept (IClassDefVisitor visitor, Object ctx);
+
+    
+    // MUTATORS:
+    
+    /**
+     * Adds a new Method_info descriptor to this collection. No duplicate
+     * checks are made. It is the responsibility of the caller to ensure
+     * that all data referenced in 'method' will eventually appear in the
+     * constant pool.
+     * 
+     * @param method new method descriptor [may not be null]
+     */
+    int add (Method_info method);
+    
+    /**
+     * Replaces the Method_info descriptor at a given offset. No duplicate
+     * checks are made. No method type compatibility checks are made.  It is
+     * the responsibility of the caller to ensure that all data referenced
+     * in 'method' will eventually appear in the constant pool.
+     * 
+     * @param offset method offset [must be in [0, size()) range; input not checked]
+     * @param method new method descriptor [may not be null]
+     * @return previous method descriptor at this offset [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Method_info set (int offset, Method_info method);
+    
+    // TODO: support this via iterators instead
+    /**
+     * Removes the Method_info descriptor at a given offset. It is
+     * the responsibility of the caller to ensure that the class definition
+     * remains consistent after this change.
+     * 
+     * @param offset method offset [must be in [0, size()) range; input not checked]
+     * @return method descriptor at this offset [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Method_info remove (int offset);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/InterfaceCollection.java b/core/java12/com/vladium/jcd/cls/InterfaceCollection.java
new file mode 100644
index 0000000..9264f07
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/InterfaceCollection.java
@@ -0,0 +1,110 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InterfaceCollection.java,v 1.1.1.1 2004/05/09 16:57:46 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.IntVector;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+final class InterfaceCollection implements IInterfaceCollection
+{
+    // public: ................................................................
+    
+    // ACCESSORS:
+
+    public int get (final int offset)
+    {
+        return m_interfaces.get (offset);
+    }
+    
+    public int size ()
+    {
+        return m_interfaces.size ();
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final InterfaceCollection _clone = (InterfaceCollection) super.clone ();
+            
+            // deep clone:
+            _clone.m_interfaces = (IntVector) m_interfaces.clone ();
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+        
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        int _interfaces_count = m_interfaces.size (); // use size() if class becomes non-final
+        out.writeU2 (_interfaces_count);
+        
+        for (int i = 0; i < _interfaces_count; i++)
+        {
+            out.writeU2 (get (i));
+        }
+    }
+    
+    // Visitor:
+    
+    public void accept (final IClassDefVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    
+    // MUTATORS:
+
+    public int add (final int interface_index)
+    {
+        final int newoffset = m_interfaces.size (); // use size() if class becomes non-final
+        m_interfaces.add (interface_index);
+        
+        return newoffset;
+    }
+    
+    public int set (final int offset, final int interface_index)
+    {
+        return m_interfaces.set (offset, interface_index);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    InterfaceCollection (final int capacity)
+    {
+        m_interfaces = capacity < 0 ? new IntVector () : new IntVector (capacity);
+    }
+
+    // private: ...............................................................
+
+    
+    private IntVector m_interfaces; // vector of constant pool indices
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/MethodCollection.java b/core/java12/com/vladium/jcd/cls/MethodCollection.java
new file mode 100644
index 0000000..f0d7340
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/MethodCollection.java
@@ -0,0 +1,143 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: MethodCollection.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.IntVector;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+final class MethodCollection implements IMethodCollection
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+        
+    public Method_info get (final int offset)
+    {
+        return (Method_info) m_methods.get (offset);
+    }
+    
+    public int [] get (final ClassDef cls, final String name)
+    {
+        if (cls == null) throw new IllegalArgumentException  ("null input: cls");
+        
+        // TODO: hash impl [not possible without having access to the parent ClassDef]
+        
+        final int count = m_methods.size (); // use size() if class becomes non-final
+        final IntVector result = new IntVector (count);
+
+        for (int m = 0; m < count; ++ m)
+        {
+            final Method_info method = (Method_info) m_methods.get (m);
+            
+            if (method.getName (cls).equals (name))  
+                result.add (m);
+        }
+        
+        return result.values (); // IntVector optimizes for the empty case 
+    }
+    
+    public int size ()
+    {
+        return m_methods.size ();
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final MethodCollection _clone = (MethodCollection) super.clone ();
+            
+            // deep clone:
+            final int methods_count = m_methods.size (); // use size() if class becomes non-final
+            _clone.m_methods = new ArrayList (methods_count);
+            for (int m = 0; m < methods_count; ++ m)
+            {
+                _clone.m_methods.add (((Method_info) m_methods.get (m)).clone ());
+            }
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        final int methods_count = m_methods.size (); // use size() if class becomes non-final
+        out.writeU2 (methods_count);
+        
+        for (int i = 0; i < methods_count; i++)
+        {
+            get (i).writeInClassFormat (out);
+        }
+    }
+    
+    // Visitor:
+    
+    public void accept (final IClassDefVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+
+    // MUTATORS:
+
+    public int add (final Method_info method)
+    {
+        final int newoffset = m_methods.size ();  // use size() if class becomes non-final
+        m_methods.add (method);
+        
+        return newoffset;
+    }
+    
+    public Method_info set (final int offset, final Method_info method)
+    {
+        return (Method_info) m_methods.set (offset, method);
+    }
+    
+    public Method_info remove (final int offset)
+    {
+        return (Method_info) m_methods.remove (offset);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    MethodCollection (final int capacity)
+    {
+        m_methods = capacity < 0 ? new ArrayList () : new ArrayList (capacity);
+    }
+
+    // private: ...............................................................
+
+    
+    private List/* Method_info */ m_methods; // method collection
+
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/jcd/cls/Method_info.java b/core/java12/com/vladium/jcd/cls/Method_info.java
new file mode 100644
index 0000000..f611d2b
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/Method_info.java
@@ -0,0 +1,220 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Method_info.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.attribute.*;
+import com.vladium.jcd.cls.constant.CONSTANT_Utf8_info;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * Each class method, and each instance initialization method <init>, is described
+ * by a variable-length method_info structure. The structure has the following
+ * format:
+ * <PRE>
+ *  method_info {
+ *          u2 access_flags;
+ *          u2 name_index;
+ *          u2 descriptor_index;
+ *          u2 attributes_count;
+ *          attribute_info attributes[attributes_count];
+ *  }
+ * </PRE>
+ * 
+ * The value of the access_flags item is a mask of modifiers used to describe
+ * access permission to and properties of a method or instance initialization method.<P>
+ * 
+ * The value of the name_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure representing either one of the special internal method names, either
+ * &lt;init&gt; or &lt;clinit&gt;, or a valid Java method name, stored as a simple
+ * (not fully qualified) name.<P>
+ * 
+ * The value of the descriptor_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure representing a valid Java method descriptor.<P>
+ * 
+ * Each value of the attributes table must be a variable-length attribute structure.
+ * A method can have any number of optional attributes associated with it. The only
+ * attributes defined by this specification for the attributes table of a method_info
+ * structure are the Code and Exceptions attributes. See {@link CodeAttribute_info}
+ * and {@link ExceptionsAttribute_info}.
+ *  
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class Method_info implements Cloneable, IAccessFlags
+{
+    // public: ................................................................
+
+    
+    public int m_name_index;
+    public int m_descriptor_index;
+    
+    
+    public Method_info (int access_flags, int name_index, int descriptor_index, IAttributeCollection attributes)
+    {
+        m_access_flags = access_flags;
+        
+        m_name_index = name_index;
+        m_descriptor_index = descriptor_index;
+        
+        m_attributes = attributes;
+    }
+
+
+    public Method_info (final IConstantCollection constants,
+                        final UDataInputStream bytes)
+        throws IOException
+    {
+        m_access_flags = bytes.readU2 ();
+        
+        m_name_index = bytes.readU2 ();
+        m_descriptor_index = bytes.readU2 ();
+        
+        // TODO: put this logic into AttributeCollection
+        
+        final int attributes_count = bytes.readU2 ();        
+        m_attributes = ElementFactory.newAttributeCollection (attributes_count);
+        
+        for (int i = 0; i < attributes_count; ++ i)
+        {
+            final Attribute_info attribute_info = Attribute_info.new_Attribute_info (constants, bytes);
+            
+            m_attributes.add (attribute_info);
+        }
+    }
+   
+    /**
+     * Returns the method name within the context of 'cls' class definition.
+     * 
+     * @param cls class that contains this method
+     * @return method name
+     */
+    public String getName (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_name_index)).m_value;
+    }
+    
+    /**
+     * Returns the descriptor string for this method within the context of 'cls'
+     * class definition.
+     * 
+     * @param cls class that contains this method
+     * @return field typename descriptor
+     */
+    public String getDescriptor (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_descriptor_index)).m_value;
+    }
+    
+    public boolean isNative ()
+    {
+        return (m_access_flags & ACC_NATIVE) != 0;
+    }
+    
+    public boolean isAbstract ()
+    {
+        return (m_access_flags & ACC_ABSTRACT) != 0;
+    }
+    
+    public boolean isSynthetic ()
+    {
+        return m_attributes.hasSynthetic ();
+    }
+    
+    public boolean isBridge ()
+    {
+        return ((m_access_flags & ACC_BRIDGE) != 0) || m_attributes.hasBridge ();
+    }
+    
+    // IAccessFlags:
+        
+    public final void setAccessFlags (final int flags)
+    {
+        m_access_flags = flags;
+    }
+
+    public final int getAccessFlags ()
+    {
+        return m_access_flags;
+    }
+    
+    
+    public IAttributeCollection getAttributes ()
+    {
+        return m_attributes;
+    }
+    
+    
+    public String toString ()
+    {
+        StringBuffer s = new StringBuffer ();
+        
+        s.append ("method_info: [modifiers: 0x" + Integer.toHexString(m_access_flags) + ", name_index = " + m_name_index + ", descriptor_index = " + m_descriptor_index + "]\n");
+        for (int i = 0; i < m_attributes.size (); i++)
+        {
+            Attribute_info attribute_info = m_attributes.get (i);
+            
+            s.append ("\t[" + i + "] attribute: " + attribute_info + "\n");
+        }
+
+        return s.toString ();
+    }
+    
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final Method_info _clone = (Method_info) super.clone ();
+            
+            // do deep copy:
+            _clone.m_attributes = (IAttributeCollection) m_attributes.clone ();
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        out.writeU2 (m_access_flags);
+        
+        out.writeU2 (m_name_index);
+        out.writeU2 (m_descriptor_index);
+        
+        m_attributes.writeInClassFormat (out);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+    
+    private int m_access_flags;
+    private IAttributeCollection m_attributes;
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/AttributeElementFactory.java b/core/java12/com/vladium/jcd/cls/attribute/AttributeElementFactory.java
new file mode 100644
index 0000000..28c15a8
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/AttributeElementFactory.java
@@ -0,0 +1,36 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AttributeElementFactory.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+ 
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class AttributeElementFactory
+{
+    // public: ................................................................
+    
+    public static IExceptionHandlerTable newExceptionHandlerTable (final int capacity)
+    {
+        return new ExceptionHandlerTable (capacity);
+    }
+    
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private AttributeElementFactory () {} // prevent subclassing
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/Attribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/Attribute_info.java
new file mode 100644
index 0000000..ff6b25b
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/Attribute_info.java
@@ -0,0 +1,204 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Attribute_info.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.cls.IConstantCollection;
+import com.vladium.jcd.cls.constant.*;
+import com.vladium.jcd.compiler.IClassFormatOutput;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * Abstract base for all XXXAttribute_info structures. It also works in conjunction
+ * with {@link GenericAttribute_info} class to process all unrecognized attributes.<P>
+ * 
+ * Attributes are used in the {@link com.vladium.jcd.cls.ClassDef}, {@link com.vladium.jcd.cls.Field_info},
+ * {@link com.vladium.jcd.cls.Method_info}, and {@link CodeAttribute_info}
+ * structures of the .class file format. All attributes have the following
+ * general format:
+ * <PRE>
+ *  attribute_info {
+ *          u2 attribute_name_index;
+ *          u4 attribute_length;
+ *          u1 info[attribute_length];
+ *  }
+ * </PRE>
+ * 
+ * For all attributes, the attribute_name_index must be a valid unsigned 16-bit
+ * index into the constant pool of the class. The constant pool entry at
+ * attribute_name_index must be a {@link com.vladium.jcd.cls.constant.CONSTANT_Utf8_info}
+ * string representing the name of the attribute. The value of the attribute_length
+ * item indicates the length of the subsequent information in bytes. The length
+ * does not include the initial six bytes that contain the attribute_name_index
+ * and attribute_length items.
+ * 
+ * @see GenericAttribute_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class Attribute_info implements Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+    
+    public static final String ATTRIBUTE_CODE               = "Code";
+    public static final String ATTRIBUTE_CONSTANT_VALUE     = "ConstantValue";
+    public static final String ATTRIBUTE_LINE_NUMBER_TABLE  = "LineNumberTable";
+    public static final String ATTRIBUTE_EXCEPTIONS         = "Exceptions";
+    public static final String ATTRIBUTE_SYNTHETIC          = "Synthetic";
+    public static final String ATTRIBUTE_BRIDGE             = "Bridge";
+    public static final String ATTRIBUTE_SOURCEFILE         = "SourceFile";
+    public static final String ATTRIBUTE_INNERCLASSES       = "InnerClasses";
+
+    /**
+     * Constant pool index for {@link com.vladium.jcd.cls.constant.CONSTANT_Utf8_info}
+     * string representing the name of this attribute [always positive].
+     */
+    public int m_name_index;
+    
+    /**
+     * Returns the name for this attribute within the constant pool context of 'cls'
+     * class definition.
+     * 
+     * @param cls class that contains this attribute
+     * @return attribute name
+     */
+    public String getName (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_name_index)).m_value;
+    }
+    
+    /**
+     * Returns the total length of this attribute when converted to
+     * .class format [including the 6-byte header]
+     */
+    public abstract long length (); // including the 6-byte header
+    
+    // Visitor:
+    
+    public abstract void accept (IAttributeVisitor visitor, Object ctx); 
+    
+    public abstract String toString ();
+    
+    // TODO: use a hashmap lookup in this method + control which set of attrs get mapped to generic
+    /**
+     * Parses out a single Attribute_info element out of .class data in
+     * 'bytes'.
+     * 
+     * @param constants constant pool for the parent class [may not be null; not validated]
+     * @param bytes input .class data stream [may not be null; not validated]
+     *  
+     * @return a single parsed attribute
+     * 
+     * @throws IOException on input errors
+     */
+    public static Attribute_info new_Attribute_info (final IConstantCollection constants,
+                                                     final UDataInputStream bytes)
+        throws IOException
+    {
+        final int attribute_name_index = bytes.readU2 ();
+        final long attribute_length = bytes.readU4 ();
+        
+        final CONSTANT_Utf8_info attribute_name = (CONSTANT_Utf8_info) constants.get (attribute_name_index);
+        final String name = attribute_name.m_value;
+                
+        if (ATTRIBUTE_CODE.equals (name))
+        {
+            return new CodeAttribute_info (constants, attribute_name_index, attribute_length, bytes);
+        }
+        else if (ATTRIBUTE_CONSTANT_VALUE.equals (name))
+        {
+            return new ConstantValueAttribute_info (attribute_name_index, attribute_length, bytes);
+        }
+        else if (ATTRIBUTE_EXCEPTIONS.equals (name))
+        {
+            return new ExceptionsAttribute_info (attribute_name_index, attribute_length, bytes);
+        }
+        else if (ATTRIBUTE_INNERCLASSES.equals (name))
+        {
+            return new InnerClassesAttribute_info (attribute_name_index, attribute_length, bytes);
+        }
+        else if (ATTRIBUTE_SYNTHETIC.equals (name))
+        {
+            return new SyntheticAttribute_info (attribute_name_index, attribute_length);
+        }
+        else if (ATTRIBUTE_BRIDGE.equals (name))
+        {
+            return new BridgeAttribute_info (attribute_name_index, attribute_length);
+        }
+        else if (ATTRIBUTE_LINE_NUMBER_TABLE.equals (name))
+        {
+            return new LineNumberTableAttribute_info (attribute_name_index, attribute_length, bytes);
+        }
+        else if (ATTRIBUTE_SOURCEFILE.equals (name))
+        {
+            return new SourceFileAttribute_info (attribute_name_index, attribute_length, bytes);
+        }
+        else
+        {
+            // default:
+            return new GenericAttribute_info (attribute_name_index, attribute_length, bytes);
+        }
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Chains to super.clone() and removes CloneNotSupportedException
+     * from the method signature.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            return super.clone ();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (UDataOutputStream out) throws IOException
+    {   
+        out.writeU2 (m_name_index);
+        out.writeU4 (length () - 6); // don't use m_attribute_length
+    }
+    
+    // protected: .............................................................
+
+    /*
+    protected Attribute_info (UDataInputStream bytes) throws IOException
+    {
+        //m_name_index = bytes.readU2 ();
+        //m_attribute_length = bytes.readU4 ();
+    }
+    */
+        
+    protected Attribute_info (final int attribute_name_index, final long attribute_length)
+    {
+        m_name_index = attribute_name_index;
+        m_attribute_length = attribute_length;
+    }
+    
+    // TODO: remove this field as it is invalidated easily by most attribute mutations
+    protected long m_attribute_length; // excluding the 6-byte header
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/BridgeAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/BridgeAttribute_info.java
new file mode 100644
index 0000000..8a43137
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/BridgeAttribute_info.java
@@ -0,0 +1,81 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: BridgeAttribute_info.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * New attribute added by J2SE 1.5
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class BridgeAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+    
+    
+    public BridgeAttribute_info (final int attribute_name_index)
+    {
+        super (attribute_name_index, 0);
+    }
+    
+    
+    public long length ()
+    {
+        return 6;
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return "BridgeAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + ']';
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {        
+        return super.clone ();    
+    }
+       
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    BridgeAttribute_info (final int attribute_name_index, final long attribute_length)
+    {
+        super (attribute_name_index, attribute_length);
+    }
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/jcd/cls/attribute/CodeAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/CodeAttribute_info.java
new file mode 100644
index 0000000..597530e
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/CodeAttribute_info.java
@@ -0,0 +1,262 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CodeAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ElementFactory;
+import com.vladium.jcd.cls.IAttributeCollection;
+import com.vladium.jcd.cls.IConstantCollection;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The Code attribute is a variable-length attribute used in the attributes
+ * table of {@link com.vladium.jcd.cls.Method_info} structures. A Code attribute
+ * contains the JVM instructions and auxiliary information for a single Java method,
+ * instance initialization method, or class or interface initialization method.
+ * Every Java Virtual Machine implementation must recognize Code attributes. There
+ * must be exactly one Code attribute in each method_info structure.<P>
+ * 
+ * The Code attribute has the format
+ * <PRE>
+ *  Code_attribute {
+ *          u2 attribute_name_index;
+ *          u4 attribute_length;
+ *          u2 max_stack;
+ *          u2 max_locals;
+ *          u4 code_length;
+ *          u1 code[code_length];
+ *          u2 exception_table_length;
+ *          {            u2 start_pc;
+ *                        u2 end_pc;
+ *                        u2  handler_pc;
+ *                        u2  catch_type;
+ *          }        exception_table[exception_table_length];
+ *
+ *          u2 attributes_count;
+ *          attribute_info attributes[attributes_count];
+ *  }
+ * </PRE>
+ *
+ * The value of the max_stack item gives the maximum number of words on the operand
+ * stack at any point during execution of this method.<P>
+ * 
+ * The value of the max_locals item gives the number of local variables used by this
+ * method, including the parameters passed to the method on invocation. The index of
+ * the first local variable is 0 . The greatest local variable index for a one-word
+ * value is max_locals-1 . The greatest local variable index for a two-word value is
+ * max_locals-2.<P>
+ * 
+ * The value of the code_length item gives the number of bytes in the code array for
+ * this method. The value of code_length must be greater than zero; the code array must
+ * not be empty.The code array gives the actual bytes of Java Virtual Machine code that
+ * implement the method.<P>
+ * 
+ * The value of the exception_table_length item gives the number of entries in the
+ * exception_table table. Each entry in the exception_table array describes one
+ * exception handler in the code array: see {@link Exception_info}.<P>
+ * 
+ * The value of the attributes_count item indicates the number of attributes of the Code
+ * attribute. Each value of the attributes table must be a variable-length attribute
+ * structure. A Code attribute can have any number of optional attributes associated
+ * with it.
+ *  
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CodeAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+
+
+    public static final byte [] EMPTY_BYTE_ARRAY = new byte [0];
+    
+    public int m_max_stack, m_max_locals;
+    
+    
+    
+    public CodeAttribute_info (final int attribute_name_index,
+                               final int max_stack, int max_locals,
+                               final byte [] code, 
+                               final IExceptionHandlerTable exceptionHandlerTable,
+                               final IAttributeCollection attributes)
+    {
+        super (attribute_name_index, 8 + (code != null ? code.length : 0) + exceptionHandlerTable.length () + attributes.length ());
+        
+        m_max_stack = max_stack;
+        m_max_locals = max_locals;
+        
+        m_code = (code != null ? code : EMPTY_BYTE_ARRAY);
+        m_codeSize = m_code.length;
+        
+        m_exceptionHandlerTable = exceptionHandlerTable;
+        m_attributes = attributes;
+    }
+    
+    /**
+     * NOTE: must also use getCodeSize() 
+     * @return
+     */
+    public final byte [] getCode ()
+    {
+        return m_code;
+    }
+    
+    public final int getCodeSize ()
+    {
+        return m_codeSize;
+    }
+    
+    public IAttributeCollection getAttributes ()
+    {
+        return m_attributes;
+    }
+    
+    public IExceptionHandlerTable getExceptionTable ()
+    {
+        return m_exceptionHandlerTable;
+    }
+    
+    public long length ()
+    {
+        return 14 + m_codeSize + m_exceptionHandlerTable.length () + m_attributes.length ();
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    
+    public String toString ()
+    {
+        String eol = System.getProperty ("line.separator");
+        
+        StringBuffer s = new StringBuffer ();
+        
+        s.append ("CodeAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + "]" + eol);
+        s.append ("    max_stack/max_locals = " + m_max_stack + '/' + m_max_locals + eol);
+        s.append ("    code [length " + m_codeSize + "]" + eol);
+        
+        for (int a = 0; a < m_attributes.size (); ++ a)
+        {
+            s.append ("         " + m_attributes.get (a) + eol);
+        }
+       
+        
+        return s.toString ();
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        final CodeAttribute_info _clone = (CodeAttribute_info) super.clone ();
+        
+        // do deep copy:
+        
+        _clone.m_code = (m_codeSize == 0 ? EMPTY_BYTE_ARRAY : (byte []) m_code.clone ()); // does not trim
+        _clone.m_exceptionHandlerTable = (IExceptionHandlerTable) m_exceptionHandlerTable.clone ();
+        _clone.m_attributes = (IAttributeCollection) m_attributes.clone ();
+        
+        return _clone;
+    }
+    
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_max_stack);
+        out.writeU2 (m_max_locals);
+    
+        out.writeU4 (m_codeSize);
+        out.write (m_code, 0, m_codeSize); // TODO: THIS IS WRONG
+        
+        m_exceptionHandlerTable.writeInClassFormat (out);
+        m_attributes.writeInClassFormat (out);
+    }
+    
+    
+    public void setCode (final byte [] code, final int codeSize)
+    {
+        m_code = code;
+        m_codeSize = codeSize;
+    }
+        
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    CodeAttribute_info (final IConstantCollection constants,
+                        final int attribute_name_index, final long attribute_length,
+                        final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        m_max_stack = bytes.readU2 ();
+        m_max_locals = bytes.readU2 ();
+        
+        final long code_length = bytes.readU4 ();
+        
+        m_code = new byte [(int) code_length];
+        bytes.readFully (m_code);
+        m_codeSize = (int) code_length;
+        
+        
+        final int exception_table_length = bytes.readU2 ();
+        m_exceptionHandlerTable = AttributeElementFactory.newExceptionHandlerTable (exception_table_length);
+        
+        for (int i = 0; i < exception_table_length; ++ i)
+        {
+            Exception_info exception_info = new Exception_info (bytes);
+            if (DEBUG) System.out.println ("\t[" + i + "] exception: " + exception_info);
+            
+            m_exceptionHandlerTable.add (exception_info);
+        }
+        
+        
+        // TODO: put this logic into AttributeCollection
+        final int attributes_count = bytes.readU2 ();
+        m_attributes = ElementFactory.newAttributeCollection (attributes_count);
+        
+        for (int i = 0; i < attributes_count; ++ i)
+        {
+            Attribute_info attribute_info = Attribute_info.new_Attribute_info (constants, bytes);
+            if (DEBUG) System.out.println ("\t[" + i + "] attribute: " + attribute_info);
+            
+            m_attributes.add (attribute_info);
+        }
+    }
+
+    // private: ...............................................................
+
+
+    private byte [] m_code; // never null [valid content extent is m_codeSize]
+    private int m_codeSize;
+    
+    private IExceptionHandlerTable m_exceptionHandlerTable; // never null
+    private IAttributeCollection m_attributes; // never null
+    
+        
+    private static final boolean DEBUG = false;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/ConstantValueAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/ConstantValueAttribute_info.java
new file mode 100644
index 0000000..f6b51d8
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/ConstantValueAttribute_info.java
@@ -0,0 +1,136 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ConstantValueAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.cls.constant.CONSTANT_literal_info;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The ConstantValue attribute is a fixed-length attribute used in the attributes
+ * table of the {@link com.vladium.jcd.cls.Field_info} structures. A ConstantValue
+ * attribute represents the value of a constant field that must be (explicitly or
+ * implicitly) static; that is, the ACC_STATIC bit in the flags item of the
+ * Field_info structure must be set. The field is not required to be final. There
+ * can be no more than one ConstantValue attribute in the attributes table of a
+ * given Field_info structure. The constant field represented by the Field_info
+ * structure is assigned the value referenced by its ConstantValue attribute as
+ * part of its initialization.<P>
+ * 
+ * The ConstantValue attribute has the format
+ * <PRE>
+ *  ConstantValue_attribute {
+ *          u2 attribute_name_index;
+ *          u4 attribute_length;
+ *          u2 constantvalue_index;
+ *  }
+ * </PRE>
+ * 
+ * The value of the constantvalue_index item must be a valid index into the constant
+ * pool table. The constant pool entry at that index must give the constant value
+ * represented by this attribute.<P>
+ * 
+ * The constant pool entry must be of a type appropriate to the field, as shown below:
+ * 
+ * <TABLE>
+ * <TR><TH>Field Type</TH>                      <TH>Entry Type</TH></TR>
+ * <TR><TD>long</TD>                            <TD>CONSTANT_Long</TD></TR>
+ * <TR><TD>float</TD>                           <TD>CONSTANT_Float</TD></TR>
+ * <TR><TD>double</TD>                          <TD>CONSTANT_Double</TD></TR>
+ * <TR><TD>int, short, char, byte, boolean</TD> <TD>CONSTANT_Integer</TD></TR>
+ * <TR><TD>java.lang.String</TD>                <TD>CONSTANT_String</TD></TR>
+ * </TABLE>
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class ConstantValueAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+
+    
+    public int m_value_index;
+    
+    
+    public ConstantValueAttribute_info (final int attribute_name_index, final int value_index)
+    {
+        super (attribute_name_index, 2);
+        
+        m_value_index = value_index;
+    }
+    
+    public CONSTANT_literal_info getValue (final ClassDef cls)
+    {
+        return (CONSTANT_literal_info) cls.getConstants ().get (m_value_index);
+    }
+    
+    public long length ()
+    {
+        return 8;
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        // TODO: return more data here
+        return "ConstantValueAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + ']';
+    
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {        
+        return super.clone ();    
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_value_index);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    ConstantValueAttribute_info (final int attribute_name_index, final long attribute_length,
+                                 final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        m_value_index = bytes.readU2 ();
+        if (DEBUG) System.out.println ("\tconstantvalue_index: " + m_value_index);
+    }
+
+    // private: ...............................................................
+
+    
+    private static final boolean DEBUG = false;
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/DeclaredExceptionTable.java b/core/java12/com/vladium/jcd/cls/attribute/DeclaredExceptionTable.java
new file mode 100644
index 0000000..c4ffd8e
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/DeclaredExceptionTable.java
@@ -0,0 +1,108 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: DeclaredExceptionTable.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+import com.vladium.util.IntVector;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+final class DeclaredExceptionTable implements IDeclaredExceptionTable
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+
+    public int get (final int offset)
+    {
+        return m_exceptions.get (offset);
+    }
+    
+    public int size ()
+    {
+        return m_exceptions.size ();
+    }
+    
+    public long length ()
+    {
+        return (1 + m_exceptions.size ()) << 1; // use size() if class becomes non-final
+    }
+        
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final DeclaredExceptionTable _clone = (DeclaredExceptionTable) super.clone ();
+            
+            // deep clone:
+            _clone.m_exceptions = (IntVector) m_exceptions.clone ();
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        int number_of_exceptions = m_exceptions.size (); // use size() if class becomes non-final
+        out.writeU2 (number_of_exceptions);
+        
+        for (int i = 0; i < number_of_exceptions; i++)
+        {
+            out.writeU2 (get (i));
+        }
+    }
+
+
+    // MUTATORS:
+        
+    public int add (final int exception_index)
+    {
+        final int newoffset = m_exceptions.size (); // use size() if class becomes non-final
+        m_exceptions.add (exception_index);
+        
+        return newoffset;
+    }
+    
+    public int set (final int offset, final int exception_index)
+    {
+        return m_exceptions.set (offset, exception_index);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    DeclaredExceptionTable (final int capacity)
+    {
+         m_exceptions = capacity < 0 ? new IntVector () : new IntVector (capacity);
+    }
+
+    // private: ...............................................................
+
+    
+    private IntVector m_exceptions;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/ExceptionHandlerTable.java b/core/java12/com/vladium/jcd/cls/attribute/ExceptionHandlerTable.java
new file mode 100644
index 0000000..074117b
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/ExceptionHandlerTable.java
@@ -0,0 +1,114 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ExceptionHandlerTable.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+final class ExceptionHandlerTable implements IExceptionHandlerTable
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+    
+    public Exception_info get (final int offset)
+    {
+        return (Exception_info) m_exceptions.get (offset);
+    }
+    
+    public int size ()
+    {
+        return m_exceptions.size ();
+    }
+    
+    public long length ()
+    {
+        return 2 + (m_exceptions.size () << 3); // use size() if class becomes non-final
+    }
+        
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            final ExceptionHandlerTable _clone = (ExceptionHandlerTable) super.clone ();
+            
+            // deep clone:
+            final int exceptions_count = m_exceptions.size (); // use size() if class becomes non-final
+            _clone.m_exceptions = new ArrayList (exceptions_count);
+            for (int e = 0; e < exceptions_count; ++ e)
+            {
+                _clone.m_exceptions.add (((Exception_info) m_exceptions.get (e)).clone ());
+            }
+            
+            return _clone;
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        int exception_table_length = m_exceptions.size (); // use size() if class becomes non-final
+        out.writeU2 (exception_table_length);
+        
+        for (int i = 0; i < exception_table_length; i++)
+        {
+            get (i).writeInClassFormat (out);
+        }
+    }
+
+
+    // MUTATORS:
+
+    public int add (final Exception_info exception)
+    {
+        final int newoffset = m_exceptions.size (); // use size() if class becomes non-final
+        m_exceptions.add (exception);
+        
+        return newoffset;
+    }
+    
+    public Exception_info set (final int offset, final Exception_info exception)
+    {
+        return (Exception_info) m_exceptions.set (offset, exception);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    ExceptionHandlerTable (final int capacity)
+    {
+        m_exceptions = capacity < 0 ? new ArrayList () : new ArrayList (capacity);
+    }
+
+    // private: ...............................................................
+
+
+    private List/* Exception_info */ m_exceptions;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/Exception_info.java b/core/java12/com/vladium/jcd/cls/attribute/Exception_info.java
new file mode 100644
index 0000000..cc9872e
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/Exception_info.java
@@ -0,0 +1,125 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Exception_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * An Exception_info is an entry layout format for {@link ExceptionHandlerTable}. Each
+ * entry contains the following items: 
+ * <PRE>
+ *    start_pc, end_pc 
+ * </PRE>
+ * The values of the two items start_pc and end_pc indicate the ranges in the code
+ * array at which the exception handler is active. The value of start_pc must be
+ * a valid index into the code array of the opcode of an instruction. The value of
+ * end_pc either must be a valid index into the code array of the opcode of an
+ * instruction, or must be equal to code_length , the length of the code array.
+ * The value of start_pc must be less than the value of end_pc.<P>
+ * 
+ * The start_pc is inclusive and end_pc is exclusive; that is, the exception handler
+ * must be active while the program counter is within the interval [start_pc, end_pc).
+ * <PRE>
+ *    handler_pc 
+ * </PRE>
+ * The value of the handler_pc item indicates the start of the exception handler.
+ * The value of the item must be a valid index into the code array, must be the index
+ * of the opcode of an instruction, and must be less than the value of the code_length
+ * item.
+ * <PRE>
+ *    catch_type
+ * </PRE>
+ * If the value of the catch_type item is nonzero, it must be a valid index into the
+ * constant_pool table. The constant_pool entry at that index must be a
+ * {@link com.vladium.jcd.cls.constant.CONSTANT_Class_info} structure representing
+ * a class of exceptions that this exception handler is designated to catch.
+ * This class must be the class Throwable or one of its subclasses. The exception
+ * handler will be called only if the thrown exception is an instance of the given
+ * class or one of its subclasses.<P>
+ * 
+ * If the value of the catch_type item is zero, this exception handler is called for
+ * all exceptions. This is used to implement finally. 
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class Exception_info implements Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    
+    public int m_start_pc, m_end_pc, m_handler_pc, m_catch_type;
+    
+    
+    public Exception_info (final int start_pc, final int end_pc,
+                           final int handler_pc, final int catch_type)
+    {
+        m_start_pc = start_pc;
+        m_end_pc = end_pc;
+        m_handler_pc = handler_pc;
+        m_catch_type = catch_type;
+    }
+    
+    
+    public String toString ()
+    {
+        return "exception_info: [start_pc/end_pc = " + m_start_pc + '/' + m_end_pc +
+               ", handler_pc = " + m_handler_pc +
+               ", catch_type = " + m_catch_type + ']';
+    }
+     
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {    
+            return super.clone ();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+      
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        out.writeU2 (m_start_pc);
+        out.writeU2 (m_end_pc);
+        out.writeU2 (m_handler_pc);
+        out.writeU2 (m_catch_type);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    Exception_info (final UDataInputStream bytes) throws IOException
+    {
+        m_start_pc = bytes.readU2 ();
+        m_end_pc = bytes.readU2 ();
+        m_handler_pc = bytes.readU2 ();
+        m_catch_type = bytes.readU2 ();
+    }
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/ExceptionsAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/ExceptionsAttribute_info.java
new file mode 100644
index 0000000..dd1837a
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/ExceptionsAttribute_info.java
@@ -0,0 +1,135 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ExceptionsAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:47 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The Exceptions attribute is a variable-length attribute used in the attributes
+ * table of a {@link com.vladium.jcd.cls.Method_info} structure. The Exceptions
+ * attribute indicates which checked exceptions a method may throw. There must be
+ * exactly one Exceptions attribute in each method_info structure.<P>
+ * 
+ * The Exceptions attribute has the following format:
+ * <PRE>
+ * Exceptions_attribute {
+ *          u2 attribute_name_index;
+ *          u4 attribute_length;
+ *          u2 number_of_exceptions;
+ *          u2 exception_index_table[number_of_exceptions];
+ *  }
+ * </PRE>
+ * The value of the number_of_exceptions item indicates the number of entries
+ * in the exception_index_table.<P>
+ * 
+ * Each nonzero value in the exception_index_table array must be a valid index
+ * into the constant_pool table. For each table item, if
+ * exception_index_table[i] != 0 , where 0 &lt; i &lt; number_of_exceptions,
+ * then the constant_pool entry at index exception_index_table[i] must be a
+ * {@link com.vladium.jcd.cls.constant.CONSTANT_Class_info} structure representing
+ * a class type that this method is declared to throw -- see {@link DeclaredExceptionTable}.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class ExceptionsAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+
+    // TODO: merge IDeclaredExceptionTable into this class
+    
+    public ExceptionsAttribute_info (final int attribute_name_index,
+                                     final IDeclaredExceptionTable exceptions)
+    {
+        super (attribute_name_index, exceptions.length ());
+    
+        m_exceptions = exceptions;
+    }
+    
+    public IDeclaredExceptionTable getDeclaredExceptions ()
+    {
+        return m_exceptions;
+    }
+    
+    public long length ()
+    {
+        return 6 + m_exceptions.length ();
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        // TODO: return more data here
+        return "ExceptionsAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + ']';
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        final ExceptionsAttribute_info _clone = (ExceptionsAttribute_info) super.clone ();
+        
+        // do deep copy:
+        _clone.m_exceptions = (IDeclaredExceptionTable) m_exceptions.clone ();
+        
+        return _clone;        
+    }
+       
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        m_exceptions.writeInClassFormat (out);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    
+    ExceptionsAttribute_info (final int attribute_name_index, final long attribute_length,
+                              final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        final int number_of_exceptions = bytes.readU2 ();
+        m_exceptions = new DeclaredExceptionTable (number_of_exceptions);
+        
+        for (int i = 0; i < number_of_exceptions; i++)
+        {
+            final int exception_index = bytes.readU2 ();
+            
+            m_exceptions.add (exception_index);
+        }
+    }
+    
+    // private: ...............................................................
+
+
+    private IDeclaredExceptionTable m_exceptions;
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/GenericAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/GenericAttribute_info.java
new file mode 100644
index 0000000..008b0d4
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/GenericAttribute_info.java
@@ -0,0 +1,104 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: GenericAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This attribute structure is used during parsing to absorb all attribute types
+ * that are not currently recognized.
+ * 
+ * @see Attribute_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class GenericAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+
+    
+    public byte [] m_info;
+    
+    
+    public GenericAttribute_info (final int attribute_name_index, final byte [] info)
+    {
+        super (attribute_name_index, (info != null ? info.length : 0));
+        
+        m_info = (info != null ? info : EMPTY_BYTE_ARRAY);
+    }
+    
+    
+    public long length ()
+    {
+        return 6 + m_info.length; 
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return "generic attribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + ']';
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        final GenericAttribute_info _clone = (GenericAttribute_info) super.clone ();
+        
+        // do deep copy:
+        _clone.m_info = (m_info.length == 0 ? EMPTY_BYTE_ARRAY : (byte []) m_info.clone ());
+        
+        return _clone;
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.write (m_info, 0, m_info.length);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    GenericAttribute_info (final int attribute_name_index, final long attribute_length,
+                           final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        m_info = new byte [(int) m_attribute_length];
+        bytes.readFully (m_info);
+    }
+
+    // private: ...............................................................
+
+    
+    private static final byte [] EMPTY_BYTE_ARRAY = new byte [0];
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/IAttributeVisitor.java b/core/java12/com/vladium/jcd/cls/attribute/IAttributeVisitor.java
new file mode 100644
index 0000000..e935968
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/IAttributeVisitor.java
@@ -0,0 +1,32 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IAttributeVisitor.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IAttributeVisitor
+{
+    // public: ................................................................
+    
+    Object visit (GenericAttribute_info attribute, Object ctx);
+    
+    Object visit (CodeAttribute_info attribute, Object ctx);
+    Object visit (ConstantValueAttribute_info attribute, Object ctx);
+    Object visit (ExceptionsAttribute_info attribute, Object ctx);
+    Object visit (LineNumberTableAttribute_info attribute, Object ctx);
+    Object visit (SourceFileAttribute_info attribute, Object ctx);
+    Object visit (SyntheticAttribute_info attribute, Object ctx);
+    Object visit (BridgeAttribute_info attribute, Object ctx);
+    Object visit (InnerClassesAttribute_info attribute, Object ctx);
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/IDeclaredExceptionTable.java b/core/java12/com/vladium/jcd/cls/attribute/IDeclaredExceptionTable.java
new file mode 100644
index 0000000..2a4ff15
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/IDeclaredExceptionTable.java
@@ -0,0 +1,84 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IDeclaredExceptionTable.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * This table is a structure nested within {@link ExceptionsAttribute_info}
+ * structure. It is a table of unsigned 16-bit indexes into constant pool. Each
+ * index points to a {@link com.vladium.jcd.cls.constant.CONSTANT_Class_info}
+ * entry representing an exception a method can throw [in unspecified order].
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IDeclaredExceptionTable extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+    
+    /**
+     * Returns the {@link com.vladium.jcd.cls.constant.CONSTANT_Class_info} constant
+     * pool index for offset'th exception type thrown by the method that contains
+     * this this exception index table in its ExceptionsAttribute_info attribute.
+     * 
+     * @param offset thrown exception class number [must be in [0, size()) range]
+     * @return constant pool index [always positive]  
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    int get (int offset);
+    
+    /**
+     * Returns the number of exception types the containing method professes
+     * to throw.
+     */
+    int size ();
+    
+    /**
+     * Returns the total length of this table when converted to
+     * .class format [including 2 count bytes]
+     */
+    long length ();
+    
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+
+
+    // MUTATORS:
+
+    /**
+     * Appends a new exception class pointer to the collection. No duplicate checks
+     * are made.
+     * 
+     * @param exception_index constant pool index [must be positive; input not validated]
+     * @return offset of the new pointer [same as {@link #size()}-1 when called
+     * after this method] 
+     */
+    int add (int exception_index);
+    
+    /**
+     * Replaces exception class pointer number 'offset' with new value 'interface_index'.
+     * No duplicate checks are made. It is the responsibility of the caller to
+     * ensure that the relevant CONSTANT_Class_info descriptor will be found
+     * in the constant pool, in the slot pointed to by 'exception_index'.
+     * 
+     * @param offset thrown exception class number [must be in [0, size()) range]
+     * @param exception_index constant pool index [must be positive; input not validated]
+     * @return previous value at the given index [always positive]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    int set (int offset, int exception_index);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/IExceptionHandlerTable.java b/core/java12/com/vladium/jcd/cls/attribute/IExceptionHandlerTable.java
new file mode 100644
index 0000000..572ac27
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/IExceptionHandlerTable.java
@@ -0,0 +1,83 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IExceptionHandlerTable.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+
+// ----------------------------------------------------------------------------
+/**
+ * This table is a structure nested within the {@link CodeAttribute_info}.
+ * It is a table of {@link Exception_info} entries, each entry representing an
+ * exception handler range. The order of these entries is the order in which
+ * a JVM will check for a matching exception handler when the parent method
+ * throws an exception.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IExceptionHandlerTable extends Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+
+    // ACCESSORS:
+    
+    /**
+     * Returns {@link Exception_info} descriptor at a given offset.
+     * 
+     * @param offset exception offset [must be in [0, size()) range; input not checked]
+     * @return Exception_info descriptor [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Exception_info get (int offset);
+    
+    /**
+     * Returns the number of descriptors in this collection [can be 0].
+     */
+    int size ();
+    
+    /**
+     * Returns the total length of this table when converted to
+     * .class format [including 2 count bytes]
+     */
+    long length ();
+    
+    // Cloneable: adjust the access level of Object.clone():
+    Object clone ();
+
+
+    // MUTATORS:
+    
+    /**
+     * Adds a new Exception_info descriptor to this collection. No duplicate
+     * checks are made. It is the responsibility of the caller to ensure
+     * that all data referenced in 'exception' will eventually be consistent
+     * with method's bytecode and the class's constant pool.
+     * 
+     * @param exception new exception descriptor [may not be null]
+     */
+    int add (Exception_info exception);
+    
+    /**
+     * Replaces the Exception_info descriptor at a given offset. No duplicate
+     * checks are made. No exception type compatibility checks are made. It is
+     * the responsibility of the caller to ensure that all data referenced
+     * in 'exception' will eventually be consistent with method's bytecode and
+     * the class's constant pool.
+     * 
+     * @param offset exception offset [must be in [0, size()) range; input not checked]
+     * @param exception new exception descriptor [may not be null]
+     * @return previous exception descriptor at this offset [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    Exception_info set (int offset, Exception_info exception);
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/attribute/InnerClass_info.java b/core/java12/com/vladium/jcd/cls/attribute/InnerClass_info.java
new file mode 100644
index 0000000..6347a3a
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/InnerClass_info.java
@@ -0,0 +1,90 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InnerClass_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class InnerClass_info implements Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+    
+    public int m_outer_class_index, m_inner_class_index;
+    public int m_inner_name_index;
+    public int m_inner_access_flags;
+    
+    
+    public InnerClass_info (final int outer_class_index, final int inner_class_index,
+                            final int inner_name_index, final int inner_access_flags) 
+    {
+        m_outer_class_index = outer_class_index;
+        m_inner_class_index = inner_class_index;
+        m_inner_name_index = inner_name_index;
+        m_inner_access_flags = inner_access_flags;
+    }
+    
+    
+    public String toString ()
+    {
+        return "innerclass_info: [m_outer_class_index = " + m_outer_class_index + ", m_inner_class_index = " + m_inner_class_index +
+            ", m_inner_name_index = " + m_inner_name_index + ", m_inner_access_flags = " + m_inner_access_flags + "]"; 
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {    
+            return super.clone ();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        out.writeU2 (m_inner_class_index);
+        out.writeU2 (m_outer_class_index);
+        out.writeU2 (m_inner_name_index);
+        out.writeU2 (m_inner_access_flags);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    InnerClass_info (final UDataInputStream bytes) throws IOException
+    {
+        m_inner_class_index = bytes.readU2 ();
+        m_outer_class_index = bytes.readU2 ();
+        m_inner_name_index = bytes.readU2 ();
+        m_inner_access_flags = bytes.readU2 ();
+    }
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/jcd/cls/attribute/InnerClassesAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/InnerClassesAttribute_info.java
new file mode 100644
index 0000000..4f24b05
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/InnerClassesAttribute_info.java
@@ -0,0 +1,164 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: InnerClassesAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class InnerClassesAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+    
+    // ACCESSORS:
+    
+    public boolean makesClassNested (final int class_index, final int [] nestedAccessFlags)
+    {
+        if (class_index > 0)
+        {
+            // TODO: avoid linear loop by keeping all class indices in a bitset
+            
+            for (int i = 0, iLimit = size (); i < iLimit; ++ i)
+            {
+                final InnerClass_info info = get (i);
+                
+                if (info.m_inner_class_index == class_index)
+                {
+                    nestedAccessFlags [0] = info.m_inner_access_flags;
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Returns {@link InnerClass_info} descriptor at a given offset.
+     * 
+     * @param offset inner class entry offset [must be in [0, size()) range;
+     * input not checked]
+     * @return InnerClass_info descriptor [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    public final InnerClass_info get (final int offset)
+    {
+        return (InnerClass_info) m_classes.get (offset);
+    }
+    
+    /**
+     * Returns the number of descriptors in this collection [can be 0].
+     */
+    public final int size ()
+    {
+        return m_classes.size ();
+    }
+    
+    public final long length ()
+    {
+        return 8 + (m_classes.size () << 3); // use size() if class becomes non-final
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ("InnerClassesAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + length () + "]\n");
+
+        for (int l = 0; l < size (); ++ l)
+        {
+            s.append ("            " + get (l));
+            s.append ("\n"); // TODO: proper EOL const
+        }
+        
+        return s.toString ();
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        final InnerClassesAttribute_info _clone = (InnerClassesAttribute_info) super.clone ();
+        
+        final List/* InnerClass_info */ classes = m_classes;
+        
+        // do deep copy:
+        final int class_count = classes.size (); // use size() if class becomes non-final
+        _clone.m_classes = new ArrayList (class_count);
+        for (int e = 0; e < class_count; ++ e)
+        {
+            _clone.m_classes.add (((InnerClass_info) classes.get (e)).clone ());
+        }
+        
+        return _clone;
+    }
+
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        final List/* InnerClass_info */ classes = m_classes;
+        
+        final int class_count = classes.size (); // use size() if class becomes non-final
+        out.writeU2 (class_count);
+        
+        for (int l = 0; l < class_count; ++ l)
+        {
+            ((InnerClass_info) classes.get (l)).writeInClassFormat (out);
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    InnerClassesAttribute_info (final int attribute_name_index, final long attribute_length,
+                                final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        final int class_count = bytes.readU2 ();
+        final List/* InnerClass_info */ classes = new ArrayList (class_count);
+        
+        for (int i = 0; i < class_count; ++ i)
+        {
+            classes.add (new InnerClass_info (bytes));
+        }
+        
+        m_classes = classes;
+    }
+    
+    // private: ...............................................................
+    
+    
+    private List/* InnerClass_info */ m_classes; // never null
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/jcd/cls/attribute/LineNumberTableAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/LineNumberTableAttribute_info.java
new file mode 100644
index 0000000..8544167
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/LineNumberTableAttribute_info.java
@@ -0,0 +1,185 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: LineNumberTableAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The LineNumberTable attribute is an optional variable-length attribute in
+ * the attributes table of a {@link CodeAttribute_info} attribute. It may be
+ * used by debuggers to determine which part of the JVM code array corresponds
+ * to a given line number in the original source file. If LineNumberTable
+ * attributes are present in the attributes table of a given Code attribute,
+ * then they may appear in any order. Furthermore, multiple LineNumberTable
+ * attributes may together represent a given line of a source file; that is,
+ * LineNumberTable attributes need not be one-to-one with source lines.<P>
+ * 
+ * The LineNumberTable attribute has the following format:
+ * <PRE>
+ * LineNumberTable_attribute {
+ *          u2 attribute_name_index;
+ *          u4 attribute_length;
+ *          u2 line_number_table_length;
+ *          {  u2 start_pc;             
+ *             u2 line_number;             
+ *          } line_number_table[line_number_table_length];
+ *  }
+ * <PRE>
+ * 
+ * LineNumberTable_attribute structure contains the following items:
+ * <PRE>
+ *    line_number_table_length
+ * </PRE>
+ * The value of the line_number_table_length item indicates the number of
+ * entries in the line_number_table array.
+ * <PRE>
+ *    line_number_table[]
+ * </PRE>
+ * Each entry in the line_number_table array indicates that the line number
+ * in the original source file changes at a given point in the code array.<P>
+ * 
+ * Each line_number_table entry must contain the following two items:
+ * <PRE>
+ *    start_pc
+ * </PRE>
+ * The value of the start_pc item must indicate the index into the code array
+ * at which the code for a new line in the original source file begins. The
+ * value of start_pc must be less than the value of the code_length item of
+ * the {@link CodeAttribute_info} attribute of which this LineNumberTable
+ * is an attribute.<P>
+ * <PRE>
+ *    line_number
+ * </PRE>
+ * The value of the line_number item must give the corresponding line number
+ * in the original source file.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class LineNumberTableAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+    
+    // ACCESSORS:
+    
+    /**
+     * Returns {@link LineNumber_info} descriptor at a given offset.
+     * 
+     * @param offset line number entry offset [must be in [0, size()) range;
+     * input not checked]
+     * @return LineNumber_info descriptor [never null]
+     * 
+     * @throws IndexOutOfBoundsException if 'offset' is outside of valid range
+     */
+    public LineNumber_info get (final int offset)
+    {
+        return (LineNumber_info) m_lines.get (offset);
+    }
+    
+    /**
+     * Returns the number of descriptors in this collection [can be 0].
+     */
+    public int size ()
+    {
+        return m_lines.size ();
+    }
+    
+    public long length ()
+    {
+        return 8 + (m_lines.size () << 2); // use size() if class becomes non-final
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ("LineNumberTableAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + length () + "]\n");
+
+        for (int l = 0; l < size (); ++ l)
+        {
+            s.append ("            " + get (l));
+            s.append ("\n"); // TODO: proper EOL const
+        }
+        
+        return s.toString ();
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        final LineNumberTableAttribute_info _clone = (LineNumberTableAttribute_info) super.clone ();
+        
+        // do deep copy:
+        final int lines_count = m_lines.size (); // use size() if class becomes non-final
+        _clone.m_lines = new ArrayList (lines_count);
+        for (int e = 0; e < lines_count; ++ e)
+        {
+            _clone.m_lines.add (((LineNumber_info) m_lines.get (e)).clone ());
+        }
+        
+        return _clone;
+    }
+
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        final int lines_count = m_lines.size (); // use size() if class becomes non-final
+        out.writeU2 (lines_count);
+        
+        for (int l = 0; l < lines_count; ++ l)
+        {
+            ((LineNumber_info) m_lines.get (l)).writeInClassFormat (out);
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    LineNumberTableAttribute_info (final int attribute_name_index, final long attribute_length,
+                                   final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        final int lines_count = bytes.readU2 ();
+        m_lines = new ArrayList (lines_count);
+        
+        for (int i = 0; i < lines_count; i++)
+        {
+            m_lines.add (new LineNumber_info (bytes));
+        }
+    }
+    
+    // private: ...............................................................
+    
+    
+    private List/* LineNumber_info */ m_lines; // never null
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/jcd/cls/attribute/LineNumber_info.java b/core/java12/com/vladium/jcd/cls/attribute/LineNumber_info.java
new file mode 100644
index 0000000..ca91e05
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/LineNumber_info.java
@@ -0,0 +1,96 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: LineNumber_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This class represents a line_number_table entry contained by
+ * {@link LineNumberTableAttribute_info} attribute. Each entry contains the
+ * following items:
+ * <PRE>
+ *    start_pc
+ * </PRE>
+ * The value of the start_pc item must indicate the index into the code array
+ * at which the code for a new line in the original source file begins. The
+ * value of start_pc must be less than the value of the code_length item of
+ * the {@link CodeAttribute_info} attribute of which this LineNumberTable
+ * is an attribute.<P>
+ * <PRE>
+ *    line_number
+ * </PRE>
+ * The value of the line_number item must give the corresponding line number
+ * in the original source file.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class LineNumber_info implements Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+    
+    public int m_start_pc, m_line_number;
+    
+    
+    public LineNumber_info (final int start_pc, final int line_number)
+    {
+        m_start_pc = start_pc;
+        m_line_number = line_number;
+    }
+    
+    public String toString ()
+    {
+        return "line_number_info: [start_pc = " + m_start_pc + ", line_number = " + m_line_number + "]";
+    }    
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {
+        try
+        {    
+            return super.clone ();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }        
+    }
+
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        out.writeU2 (m_start_pc);
+        out.writeU2 (m_line_number);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    LineNumber_info (final UDataInputStream bytes) throws IOException
+    {
+        m_start_pc = bytes.readU2 ();
+        m_line_number = bytes.readU2 ();
+    }
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/jcd/cls/attribute/SourceFileAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/SourceFileAttribute_info.java
new file mode 100644
index 0000000..a1c1a2d
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/SourceFileAttribute_info.java
@@ -0,0 +1,97 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SourceFileAttribute_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.cls.constant.CONSTANT_Utf8_info;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class SourceFileAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+    
+    
+    public int m_sourcefile_index;
+    
+    
+    public SourceFileAttribute_info (final int attribute_name_index)
+    {
+        super (attribute_name_index, 0);
+    }
+    
+    
+    public long length ()
+    {
+        return 8;
+    }
+    
+    public CONSTANT_Utf8_info getSourceFile (final ClassDef cls)
+    {
+        return (CONSTANT_Utf8_info) cls.getConstants ().get (m_sourcefile_index);
+    }  
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return "SourceFileAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + ']';
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {        
+        return super.clone ();    
+    }
+       
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_sourcefile_index);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    SourceFileAttribute_info (final int attribute_name_index, final long attribute_length,
+                              final UDataInputStream bytes)
+        throws IOException
+    {
+        super (attribute_name_index, attribute_length);
+        
+        m_sourcefile_index = bytes.readU2 ();
+    }
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/jcd/cls/attribute/SyntheticAttribute_info.java b/core/java12/com/vladium/jcd/cls/attribute/SyntheticAttribute_info.java
new file mode 100644
index 0000000..0266970
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/attribute/SyntheticAttribute_info.java
@@ -0,0 +1,98 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SyntheticAttribute_info.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.attribute;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The Synthetic attribute is a fixed-length attribute in the attributes table
+ * of ClassFile, {@link com.vladium.jcd.cls.Field_info}, and
+ * {@link com.vladium.jcd.cls.Method_info} structures. A class member that does
+ * not appear in the source code must be marked using a Synthetic attribute.<P>
+ * 
+ * The Synthetic attribute has the following format:
+ * <PRE>
+ * Synthetic_attribute {
+ *          u2 attribute_name_index;
+ *          u4 attribute_length;
+ * }
+ * </PRE>
+ * 
+ * The value of the attribute_name_index item must be a valid index into the
+ * constant_pool table. The constant_pool entry at that index must be a CONSTANT_Utf8_info
+ * structure representing the string "Synthetic".<P>
+ * 
+ * The value of the attribute_length item is zero.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class SyntheticAttribute_info extends Attribute_info
+{
+    // public: ................................................................
+    
+    
+    public SyntheticAttribute_info (final int attribute_name_index)
+    {
+        super (attribute_name_index, 0);
+    }
+    
+    
+    public long length ()
+    {
+        return 6;
+    }
+    
+    // Visitor:
+    
+    public void accept (final IAttributeVisitor visitor, final Object ctx)
+    {
+        visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return "SyntheticValueAttribute_info: [attribute_name_index = " + m_name_index + ", attribute_length = " + m_attribute_length + ']';
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Performs a deep copy.
+     */
+    public Object clone ()
+    {        
+        return super.clone ();    
+    }
+       
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    SyntheticAttribute_info (final int attribute_name_index, final long attribute_length)
+    {
+        super (attribute_name_index, attribute_length);
+    }
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Class_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Class_info.java
new file mode 100644
index 0000000..07984a3
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Class_info.java
@@ -0,0 +1,98 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Class_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_Class_info structure is used to represent a class or an interface.<P>
+ * 
+ * The value of the name_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure representing a valid fully qualified Java class name that has been
+ * converted to the class file's internal form. 
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Class_info extends CONSTANT_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 7;
+    
+    public int m_name_index;
+    
+    
+    public CONSTANT_Class_info (final int name_index)
+    {
+        m_name_index = name_index;
+    }
+
+    
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    /**
+     * Returns the JVM class name within the constant pool context of 'cls'
+     * class definition.
+     * 
+     * @param cls class that contains this constant
+     * @return class name [in JVM format]
+     */
+    public String getName (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_name_index)).m_value;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    } 
+
+    
+    public String toString ()
+    {
+        return "CONSTANT_Class: [name_index = " + m_name_index + ']';
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_name_index);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Class_info (final UDataInputStream bytes) throws IOException
+    {
+        m_name_index = bytes.readU2 ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Double_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Double_info.java
new file mode 100644
index 0000000..a662092
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Double_info.java
@@ -0,0 +1,91 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Double_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The {@link CONSTANT_Long_info} and CONSTANT_Double_info represent eight-byte
+ * numeric (long and double) constants.<P>
+ * 
+ * The high_bytes and low_bytes items of the CONSTANT_Double_info structure contain
+ * the double value in IEEE 754 floating-point "double format" bit layout.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Double_info extends CONSTANT_literal_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 6;
+    
+    public double m_value;
+    
+    
+    public CONSTANT_Double_info (final double value)
+    {
+        m_value = value;    
+    }
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return Double.toString (m_value);
+    }
+    
+    /**
+     * Overrides the default implementation to return '2'.
+     */
+    public int width ()
+    {
+        return 2;
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeDouble (m_value);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Double_info (final UDataInputStream bytes) throws IOException
+    {
+        m_value = bytes.readDouble ();    
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Fieldref_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Fieldref_info.java
new file mode 100644
index 0000000..0859227
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Fieldref_info.java
@@ -0,0 +1,73 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Fieldref_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This structure is used in the constant pool to represent dynamic references
+ * to fields. The class_index item of a CONSTANT_Fieldref_info or a
+ * {@link CONSTANT_Methodref_info} structure must be a class type, not an
+ * interface type. 
+ * 
+ * @see CONSTANT_ref_info
+ * @see CONSTANT_Methodref_info
+ * @see CONSTANT_InterfaceMethodref_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Fieldref_info extends CONSTANT_ref_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 9;
+    
+    
+    public CONSTANT_Fieldref_info (final int class_index, final int name_and_type_index)
+    {
+        super (class_index, name_and_type_index);
+    }
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return "CONSTANT_Fieldref: [class_index = " + m_class_index + ", name_and_type_index = " + m_name_and_type_index + ']';
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Fieldref_info (final UDataInputStream bytes) throws IOException
+    {
+        super (bytes);
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Float_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Float_info.java
new file mode 100644
index 0000000..5d7d27a
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Float_info.java
@@ -0,0 +1,82 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Float_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_Integer_info and CONSTANT_Float_info structures represent
+ * four-byte numeric (int and float) constants.<P>
+ * 
+ * The bytes item of the CONSTANT_Float_info structure contains the value of
+ * the float constant in IEEE 754 floating-point "single format" bit layout.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Float_info extends CONSTANT_literal_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 4;
+    
+    public float m_value;
+    
+    
+    public CONSTANT_Float_info (final float value)
+    {
+        m_value = value;
+    }
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return Float.toString (m_value);
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeFloat (m_value);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Float_info (final UDataInputStream bytes) throws IOException
+    {
+        m_value = bytes.readFloat ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Integer_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Integer_info.java
new file mode 100644
index 0000000..a6ffe5a
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Integer_info.java
@@ -0,0 +1,83 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Integer_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_Integer_info and CONSTANT_Float_info structures represent
+ * four-byte numeric (int and float) constants.<P>
+ * 
+ * The bytes item of the CONSTANT_Integer_info structure contains the value of
+ * the int constant. The bytes of the value are stored in big-endian (high byte
+ * first) order.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Integer_info extends CONSTANT_literal_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 3;
+    
+    public int m_value;
+    
+    
+    public CONSTANT_Integer_info (final int value)
+    {
+        m_value = value;
+    }
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return Integer.toString (m_value);
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeInt (m_value);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Integer_info (final UDataInputStream bytes) throws IOException
+    {
+        m_value = bytes.readInt ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_InterfaceMethodref_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_InterfaceMethodref_info.java
new file mode 100644
index 0000000..76113c3
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_InterfaceMethodref_info.java
@@ -0,0 +1,72 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_InterfaceMethodref_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This structure is used in the constant pool to represent dynamic references
+ * to interface methods. The class_index item of a CONSTANT_InterfaceMethodref_info
+ * structure must be an interface type that declares the given method. 
+ * 
+ * @see CONSTANT_ref_info
+ * @see CONSTANT_Fieldref_info
+ * @see CONSTANT_Methodref_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_InterfaceMethodref_info extends CONSTANT_ref_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 11;
+    
+    
+    public CONSTANT_InterfaceMethodref_info (final int class_index, final int name_and_type_index)
+    {
+        super (class_index, name_and_type_index);
+    }
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+        
+    public String toString ()
+    {
+        return "CONSTANT_InterfaceMethodref: [class_index = " + m_class_index + ", name_and_type_index = " + m_name_and_type_index + ']';
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_InterfaceMethodref_info (final UDataInputStream bytes) throws IOException
+    {
+        super (bytes);
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Long_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Long_info.java
new file mode 100644
index 0000000..22f7bc4
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Long_info.java
@@ -0,0 +1,92 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Long_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_Long_info and {@link CONSTANT_Double_info} represent eight-byte
+ * numeric (long and double) constants.<P>
+ * 
+ * The unsigned high_bytes and low_bytes items of the CONSTANT_Long_info structure
+ * together contain the value of the long constant
+ * (( long ) high_bytes << 32) + low_bytes , where the bytes of each of high_bytes
+ * and low_bytes are stored in big-endian (high byte first) order.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Long_info extends CONSTANT_literal_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 5;
+    
+    public long m_value;
+    
+    
+    public CONSTANT_Long_info (final long value)
+    {
+        m_value = value;
+    }
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return Long.toString (m_value);
+    }
+    
+    /**
+     * Overrides the default implementation to return '2'.
+     */
+    public int width ()
+    {
+        return 2;
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeLong (m_value);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Long_info (final UDataInputStream bytes) throws IOException
+    {
+        m_value = bytes.readLong ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Methodref_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Methodref_info.java
new file mode 100644
index 0000000..149c5cb
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Methodref_info.java
@@ -0,0 +1,73 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Methodref_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This structure is used in the constant pool to represent dynamic references
+ * to class methods. The class_index item of a {@link CONSTANT_Fieldref_info} or
+ * a CONSTANT_Methodref_info structure must be a class type, not an interface type.
+ *  
+ * @see CONSTANT_ref_info
+ * @see CONSTANT_Fieldref_info
+ * @see CONSTANT_InterfaceMethodref_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Methodref_info extends CONSTANT_ref_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 10;
+    
+    
+    public CONSTANT_Methodref_info (final int class_index, final int name_and_type_index)
+    {
+        super (class_index, name_and_type_index);
+    }
+
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    } 
+    
+    public String toString ()
+    {
+        return "CONSTANT_Methodref_info: [class_index = " + m_class_index + ", name_and_type_index = " + m_name_and_type_index + ']';
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Methodref_info (final UDataInputStream bytes) throws IOException
+    {
+        super (bytes);
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_NameAndType_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_NameAndType_info.java
new file mode 100644
index 0000000..d27a4f7
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_NameAndType_info.java
@@ -0,0 +1,104 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_NameAndType_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_NameAndType_info structure is used to represent a field or method,
+ * without indicating which class or interface type it belongs to.<P>
+ * 
+ * The value of the name_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure representing a valid Java field name or method name stored as a simple
+ * (not fully qualified) name, that is, as a Java identifier.<P>
+ * 
+ * The value of the descriptor_index item must be a valid index into the constant
+ * pool table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure representing a valid Java field descriptor or method descriptor.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_NameAndType_info extends CONSTANT_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 12;
+    
+    
+    public int m_name_index;
+    public int m_descriptor_index;
+    
+    
+    public CONSTANT_NameAndType_info (final int name_index, final int descriptor_index)
+    {
+        m_name_index = name_index;
+        m_descriptor_index = descriptor_index;
+    }
+    
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    public String getName (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_name_index)).m_value;
+    }
+    
+    public String getDescriptor (final ClassDef cls)
+    {
+        return ((CONSTANT_Utf8_info) cls.getConstants ().get (m_descriptor_index)).m_value;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+     
+    public String toString ()
+    {
+        return "CONSTANT_NameAndType: [name_index = " + m_name_index + ", descriptor_index = " + m_descriptor_index + ']';
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_name_index);
+        out.writeU2 (m_descriptor_index);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_NameAndType_info (final UDataInputStream bytes) throws IOException
+    {
+        m_name_index = bytes.readU2 ();
+        m_descriptor_index = bytes.readU2 ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_String_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_String_info.java
new file mode 100644
index 0000000..82f495a
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_String_info.java
@@ -0,0 +1,85 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_String_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_String_info structure is used to represent constant objects of
+ * the type java.lang.String.<P>
+ * 
+ * The value of the string_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Utf8_info}
+ * structure representing the sequence of characters to which the
+ * java.lang.String object is to be initialized.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public 
+final class CONSTANT_String_info extends CONSTANT_literal_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 8;
+    
+    public int m_string_index;
+    
+    
+    public CONSTANT_String_info (final int string_index)
+    {
+        m_string_index = string_index;
+    }
+
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    }
+    
+    public String toString ()
+    {
+        return "CONSTANT_String: [string_index = " + m_string_index + ']';
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_string_index);    
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_String_info (final UDataInputStream bytes) throws IOException
+    {
+        m_string_index = bytes.readU2 ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Utf8_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Utf8_info.java
new file mode 100644
index 0000000..5d2eaf1
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_Utf8_info.java
@@ -0,0 +1,87 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_Utf8_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * The CONSTANT_Utf8_info structure is used to represent constant string values.<P>
+ * 
+ * The bytes of multibyte characters are stored in the class file in big-endian
+ * (high byte first) order. There are two differences between this format and the
+ * "standard" UTF-8 format. First, the null byte (byte)0 is encoded using the
+ * two-byte format rather than the one-byte format, so that Java Virtual Machine
+ * UTF-8 strings never have embedded nulls. Second, only the one-byte, two-byte,
+ * and three-byte formats are used. The Java Virtual Machine does not recognize
+ * the longer UTF-8 formats.  
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class CONSTANT_Utf8_info extends CONSTANT_info
+{
+    // public: ................................................................
+
+    public static final byte TAG = 1;
+    
+    public String m_value;
+    
+    
+    public CONSTANT_Utf8_info (final String value)
+    {
+        m_value = value;
+    }
+
+
+    public final byte tag ()
+    {
+        return TAG;
+    }
+    
+    // Visitor:
+    
+    public Object accept (final ICONSTANTVisitor visitor, final Object ctx)
+    {
+        return visitor.visit (this, ctx);
+    } 
+    
+    public String toString ()
+    {
+        return "CONSTANT_Utf8: [" + m_value + ']';
+    }
+       
+    // Cloneable: inherited clone() is Ok
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeUTF (m_value);
+    }
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_Utf8_info (final UDataInputStream bytes) throws IOException
+    {
+        m_value = bytes.readUTF ();
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_info.java
new file mode 100644
index 0000000..c078847
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_info.java
@@ -0,0 +1,203 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_info.java,v 1.1.1.1 2004/05/09 16:57:48 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.compiler.IClassFormatOutput;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * An abstract base for all other CONSTANT_XXX_info structures. See $4.4 in VM
+ * spec 1.0 for all such structure definitions.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class CONSTANT_info implements Cloneable, IClassFormatOutput
+{
+    // public: ................................................................
+    
+    
+    /**
+     * Returns the tag byte for this CONSTANT type [this data is
+     * static class data].
+     */
+    public abstract byte tag ();
+    
+    // Visitor:
+    
+    public abstract Object accept (ICONSTANTVisitor visitor, Object ctx); 
+    
+    public abstract String toString ();
+    
+    /**
+     * Returns the number of constant pool index slots occupied by this
+     * CONSTANT type. This implementation defaults to returning '1'.
+     * 
+     * @see CONSTANT_Long_info
+     * @see CONSTANT_Long_info
+     * 
+     * @return int
+     */
+    public int width ()
+    {
+        return 1;
+    }
+    
+    
+    /**
+     * Virtual constructor method for all CONSTANT_XXX_info structures.
+     */
+    public static CONSTANT_info new_CONSTANT_info (final UDataInputStream bytes)
+        throws IOException
+    {
+        byte tag = bytes.readByte ();                                                                                   
+        
+        switch (tag)
+        {
+        case CONSTANT_Utf8_info.TAG:
+            return new CONSTANT_Utf8_info (bytes);
+            
+        case CONSTANT_Integer_info.TAG:
+            return new CONSTANT_Integer_info (bytes);
+            
+        case CONSTANT_Float_info.TAG:
+            return new CONSTANT_Float_info (bytes);
+            
+        case CONSTANT_Long_info.TAG:
+            return new CONSTANT_Long_info (bytes);
+            
+        case CONSTANT_Double_info.TAG:
+            return new CONSTANT_Double_info (bytes);
+        
+            
+        case CONSTANT_Class_info.TAG:
+            return new CONSTANT_Class_info (bytes);
+            
+        case CONSTANT_String_info.TAG:
+            return new CONSTANT_String_info (bytes);
+            
+            
+        case CONSTANT_Fieldref_info.TAG:
+            return new CONSTANT_Fieldref_info (bytes);
+            
+        case CONSTANT_Methodref_info.TAG:
+            return new CONSTANT_Methodref_info (bytes);
+            
+        case CONSTANT_InterfaceMethodref_info.TAG:
+            return new CONSTANT_InterfaceMethodref_info (bytes);
+            
+            
+        case CONSTANT_NameAndType_info.TAG:
+            return new CONSTANT_NameAndType_info (bytes);
+            
+        default: throw new IllegalStateException ("CONSTANT_info: invalid tag value [" + tag + ']');
+                 
+        } // end of switch
+    }
+    
+    // Cloneable:
+    
+    /**
+     * Chains to super.clone() and removes CloneNotSupportedException
+     * from the method signature.
+     */
+    public Object clone ()
+    {
+        try
+        {
+            return super.clone ();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new InternalError (e.toString ());
+        }
+    }
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {    
+        out.writeByte (tag ());
+    }
+    
+    public static String tagToString (final CONSTANT_info constant)
+    {
+        switch (constant.tag ())
+        {
+        case CONSTANT_Utf8_info.TAG:
+            return "CONSTANT_Utf8";
+            
+        case CONSTANT_Integer_info.TAG:
+            return "CONSTANT_Integer";
+            
+        case CONSTANT_Float_info.TAG:
+            return "CONSTANT_Float";
+            
+        case CONSTANT_Long_info.TAG:
+            return "CONSTANT_Long";
+            
+        case CONSTANT_Double_info.TAG:
+            return "CONSTANT_Double";
+        
+            
+        case CONSTANT_Class_info.TAG:
+            return "CONSTANT_Class";
+            
+        case CONSTANT_String_info.TAG:
+            return "CONSTANT_String";
+            
+            
+        case CONSTANT_Fieldref_info.TAG:
+            return "CONSTANT_Fieldref";
+            
+        case CONSTANT_Methodref_info.TAG:
+            return "CONSTANT_Methodref";
+            
+        case CONSTANT_InterfaceMethodref_info.TAG:
+            return "CONSTANT_InterfaceMethodref";
+            
+            
+        case CONSTANT_NameAndType_info.TAG:
+            return "CONSTANT_NameAndType";
+            
+        default: throw new IllegalStateException ("CONSTANT_info: invalid tag value [" + constant.tag () + ']');
+                 
+        } // end of switch
+    }
+    
+    // protected: .............................................................
+
+    /*
+    protected static final byte CONSTANT_Utf8                       = 1;
+    protected static final byte CONSTANT_Integer                    = 3;
+    protected static final byte CONSTANT_Float                      = 4;
+    protected static final byte CONSTANT_Long                       = 5;
+    protected static final byte CONSTANT_Double                     = 6;
+    protected static final byte CONSTANT_Class                      = 7;
+    protected static final byte CONSTANT_String                     = 8;
+    protected static final byte CONSTANT_Fieldref                   = 9;
+    protected static final byte CONSTANT_Methodref                  = 10;
+    protected static final byte CONSTANT_InterfaceMethodref         = 11;
+    protected static final byte CONSTANT_NameAndType                = 12;
+    */
+    
+    protected CONSTANT_info ()
+    {
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_literal_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_literal_info.java
new file mode 100644
index 0000000..316526c
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_literal_info.java
@@ -0,0 +1,43 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_literal_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+// ----------------------------------------------------------------------------
+/**
+ * Abstract base for all CONSTANT_XXX_info structures representing literal values
+ * in the constant pool.
+ * 
+ * @see CONSTANT_Integer_info
+ * @see CONSTANT_Long_info
+ * @see CONSTANT_Float_info
+ * @see CONSTANT_Double_info
+ * @see CONSTANT_String_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class CONSTANT_literal_info extends CONSTANT_info
+{
+    // public: ................................................................
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_literal_info ()
+    {
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/CONSTANT_ref_info.java b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_ref_info.java
new file mode 100644
index 0000000..1ffd7a5
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/CONSTANT_ref_info.java
@@ -0,0 +1,80 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CONSTANT_ref_info.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * Abstract base for all CONSTANT_XXXref_info structures. They all have a constant
+ * pool pointer to a {@link CONSTANT_Class_info} and {@link CONSTANT_NameAndType_info}
+ * entries.<P>
+ * 
+ * The value of the class_index item must be a valid index into the constant pool
+ * table. The constant pool entry at that index must be a {@link CONSTANT_Class_info}
+ * structure representing the class or interface type that contains the declaration
+ * of the field or method.<P>
+ * 
+ * The class_index item of a {@link CONSTANT_Fieldref_info} or a {@link CONSTANT_Methodref_info}
+ * structure must be a class type, not an interface type. The class_index item of
+ * a {@link CONSTANT_InterfaceMethodref_info} structure must be an interface type
+ * that declares the given method.
+ *
+ * @see CONSTANT_Fieldref_info
+ * @see CONSTANT_Methodref_info
+ * @see CONSTANT_InterfaceMethodref_info
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class CONSTANT_ref_info extends CONSTANT_info
+{
+    // public: ................................................................
+
+    
+    public int m_class_index;
+    public int m_name_and_type_index;
+    
+    // IClassFormatOutput:
+    
+    public void writeInClassFormat (final UDataOutputStream out) throws IOException
+    {
+        super.writeInClassFormat (out);
+        
+        out.writeU2 (m_class_index);
+        out.writeU2 (m_name_and_type_index);
+    }
+    
+    // Cloneable: inherited clone() is Ok
+    
+    // protected: .............................................................
+
+    
+    protected CONSTANT_ref_info (final UDataInputStream bytes)
+        throws IOException
+    {
+        m_class_index = bytes.readU2 ();
+        m_name_and_type_index = bytes.readU2 ();
+    }
+    
+    protected CONSTANT_ref_info (final int class_index, final int name_and_type_index)
+    {
+        m_class_index = class_index;
+        m_name_and_type_index = name_and_type_index;
+    }
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/cls/constant/ICONSTANTVisitor.java b/core/java12/com/vladium/jcd/cls/constant/ICONSTANTVisitor.java
new file mode 100644
index 0000000..c6f0d22
--- /dev/null
+++ b/core/java12/com/vladium/jcd/cls/constant/ICONSTANTVisitor.java
@@ -0,0 +1,38 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ICONSTANTVisitor.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.cls.constant;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface ICONSTANTVisitor
+{
+    // public: ................................................................
+    
+    //Object visit (CONSTANT_info constant, Object ctx);
+    
+    Object visit (CONSTANT_Class_info constant, Object ctx);
+    
+    Object visit (CONSTANT_InterfaceMethodref_info constant, Object ctx);
+    Object visit (CONSTANT_Methodref_info constant, Object ctx);
+    Object visit (CONSTANT_Fieldref_info constant, Object ctx);
+
+    Object visit (CONSTANT_Double_info constant, Object ctx);
+    Object visit (CONSTANT_Float_info constant, Object ctx);
+    Object visit (CONSTANT_Integer_info constant, Object ctx);
+    Object visit (CONSTANT_Long_info constant, Object ctx);
+    Object visit (CONSTANT_String_info constant, Object ctx);
+
+    Object visit (CONSTANT_Utf8_info constant, Object ctx);
+    Object visit (CONSTANT_NameAndType_info constant, Object ctx);
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/compiler/ClassWriter.java b/core/java12/com/vladium/jcd/compiler/ClassWriter.java
new file mode 100644
index 0000000..6154b66
--- /dev/null
+++ b/core/java12/com/vladium/jcd/compiler/ClassWriter.java
@@ -0,0 +1,40 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassWriter.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.compiler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import com.vladium.jcd.cls.*;
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class ClassWriter
+{
+    // public: ................................................................
+    
+
+    public static void writeClassTable (final ClassDef classTable, final OutputStream out)
+        throws IOException
+    {
+        classTable.writeInClassFormat (new UDataOutputStream (out));
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/compiler/CodeGen.java b/core/java12/com/vladium/jcd/compiler/CodeGen.java
new file mode 100644
index 0000000..c561f4e
--- /dev/null
+++ b/core/java12/com/vladium/jcd/compiler/CodeGen.java
@@ -0,0 +1,133 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: CodeGen.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.compiler;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.cls.constant.CONSTANT_Integer_info;
+import com.vladium.jcd.opcodes.IOpcodes;
+import com.vladium.util.ByteArrayOStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class CodeGen implements IOpcodes
+{
+    // public: ................................................................
+    
+    
+    public static void load_local_object_var (final ByteArrayOStream out, final int index)
+    {
+        if (index <= 3)
+        {
+            out.write (_aload_0 + index); // aload_n
+        }
+        else if (index <= 0xFF)
+        {
+            out.write2 (_aload,
+                        index);  // indexbyte
+        }
+        else
+        {
+            out.write4 (_wide,
+                        _aload,
+                        index >>> 8,    // indexbyte1
+                        index);         // indexbyte2
+        }
+    }
+    
+    public static void store_local_object_var (final ByteArrayOStream out, final int index)
+    {
+        if (index <= 3)
+        {
+            out.write (_astore_0 + index); // astore_n
+        }
+        else if (index <= 0xFF)
+        {
+            out.write2 (_astore,
+                        index);  // indexbyte
+        }
+        else
+        {
+            out.write4 (_wide,
+                        _astore,
+                        index >>> 8,    // indexbyte1
+                        index);         // indexbyte2
+        }
+        
+        // [stack -1]
+    }
+    
+    public static void push_int_value (final ByteArrayOStream out, final ClassDef cls, final int value)
+    {
+        if ((-1 <= value) && (value <= 5))
+        {
+            out.write (_iconst_0 + value);
+        }
+        else if ((-128 <= value) && (value <= 127))
+        {
+            out.write2 (_bipush,
+                        value); // byte1
+        }
+        else if ((-32768 <= value) && (value <= 32767))
+        {
+            out.write3 (_sipush,
+                        value >>> 8,    // byte1
+                        value);         // byte2
+        }
+        else // we have to create an Integer constant in the constant pool:
+        {
+            // TODO: check if it's already there
+            final int index = cls.getConstants ().add (new CONSTANT_Integer_info (value));
+            
+            if (index <= 0xFF)
+            {
+                out.write2 (_ldc,
+                            index);  // index
+            }
+            else // must use ldc_w
+            {
+                out.write3 (_ldc_w,
+                            index >>> 8,  // indexbyte1
+                            index);       // indexbyte2
+            }
+        }
+        
+        // [stack +1]
+    }
+    
+    public static void push_constant_index (final ByteArrayOStream out, final int index)
+    {
+        if (index <= 0xFF)
+        {
+            out.write2 (_ldc,
+                       index);  // indexbyte
+        }
+        else
+        {
+            out.write3 (_ldc_w,
+                        index >>> 8,     // indexbyte1
+                        index);          // indexbyte2
+        }
+        
+        // [stack +1]
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private CodeGen () {} // prevent subclassing
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/compiler/IClassFormatOutput.java b/core/java12/com/vladium/jcd/compiler/IClassFormatOutput.java
new file mode 100644
index 0000000..0e70540
--- /dev/null
+++ b/core/java12/com/vladium/jcd/compiler/IClassFormatOutput.java
@@ -0,0 +1,30 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IClassFormatOutput.java,v 1.1.1.1 2004/05/09 16:57:49 vlad_r Exp $
+ */
+package com.vladium.jcd.compiler;
+
+import java.io.IOException;
+
+import com.vladium.jcd.lib.UDataOutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * Our class tables and nested table structures support this interface so that
+ * they could be serialized into binary format as per .class layout format.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+interface IClassFormatOutput
+{
+    // public: ................................................................
+
+    void writeInClassFormat (UDataOutputStream out) throws IOException;
+    
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/lib/Types.java b/core/java12/com/vladium/jcd/lib/Types.java
new file mode 100644
index 0000000..05b4230
--- /dev/null
+++ b/core/java12/com/vladium/jcd/lib/Types.java
@@ -0,0 +1,756 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Types.java,v 1.1.1.1 2004/05/09 16:57:50 vlad_r Exp $
+ */
+package com.vladium.jcd.lib;
+
+import java.io.IOException;
+import java.lang.reflect.*;
+
+import com.vladium.jcd.cls.IAccessFlags;
+
+// ----------------------------------------------------------------------------
+/**
+ * Utility methods for manipulating type signatures and descriptors.
+ * 
+ * TODO: fix usage of chars in parsers
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public abstract class Types
+{
+    // public: ................................................................
+    
+    /**
+     * Returns 'c''s package name [does not include trailing '.'] or ""
+     * if 'c' is in the default package.
+     */
+    public static String getClassPackageName (final Class c)
+    {
+        // TODO: handle array and other types
+        
+        final String className = c.getName ();
+        final int lastDot = className.lastIndexOf ('.');
+        return lastDot >= 0 ? className.substring (0, lastDot) : "";
+    }
+    
+    
+    public static String accessFlagsToString (final int flags, final boolean isClass)
+    {
+        final StringBuffer result = new StringBuffer ();
+      
+        boolean first = true;
+        
+        if (isClass)
+        {
+            for (int f = 0; f < IAccessFlags.ALL_ACC.length; ++ f)
+            {
+                final int bit = IAccessFlags.ALL_ACC [f];
+                
+                if ((flags & bit) != 0)
+                {
+                    if (first)
+                        first = false;
+                    else
+                        result.append (" ");
+                    
+                    if (bit == IAccessFlags.ACC_SUPER)
+                        result.append ("super");
+                    else
+                        result.append (IAccessFlags.ALL_ACC_NAMES [f]);
+                }
+            }
+        }
+        else
+        {
+            for (int f = 0; f < IAccessFlags.ALL_ACC.length; ++ f)
+            {
+                final int bit = IAccessFlags.ALL_ACC [f];
+                
+                if ((flags & bit) != 0)
+                {
+                    if (first)
+                        first = false;
+                    else
+                        result.append (" ");
+                        
+                    result.append (IAccessFlags.ALL_ACC_NAMES [f]);
+                }
+            }
+        }
+        
+        return result.toString ();
+    } 
+
+    
+    /**
+     * Converts Java-styled package/class name to how it would be
+     * represented in the VM.<P>
+     * 
+     * Example:<BR>
+     * javaNameToVMName("java.lang.Object") = "java/lang/Object"
+     * 
+     * @see #vmNameToJavaName
+     */
+    public static String javaNameToVMName (final String javaName)
+    {
+        if (javaName == null) return null;
+        return javaName.replace ('.', '/');
+    }
+    
+    
+    /**
+     * Converts a VM-styled package/class name to how it would be
+     * represented in Java.<P>
+     * 
+     * Example:<BR>
+     * vmNameToJavaName("java/lang/Object") = "java.lang.Object"
+     * 
+     * @see #javaNameToVMName
+     */
+    public static String vmNameToJavaName (final String vmName)
+    {
+        if (vmName == null) return null;
+        return vmName.replace ('/', '.');
+    }
+    
+    
+    /**
+     * Converts a method signature to its VM descriptor representation.
+     * See $4.3 of the VM spec 1.0 for the descriptor grammar.<P>
+     * 
+     * Example:<BR>
+     * signatureToDescriptor(new Object().getClass().getMethod("equals" ,new Class[0])) = "(Ljava/lang/Object;)Z"
+     * <P>
+     * 
+     * Equivalent to
+     * <CODE>signatureToDescriptor(method.getParameterTypes (), method.getReturnType ())</CODE>.
+     */
+    public static String signatureToDescriptor (Method method)
+    {
+        if (method == null) throw new IllegalArgumentException ("null input: method");
+        return signatureToDescriptor (method.getParameterTypes (), method.getReturnType ());
+    }
+    
+    
+    /**
+     * Converts a method signature (parameter types + return type) to its VM descriptor
+     * representation. See $4.3 of the VM spec 1.0 for the descriptor grammar.<P>
+     */
+    public static String signatureToDescriptor (Class [] parameterTypes, Class returnType)
+    {
+        return new signatureCompiler ().signatureDescriptor (parameterTypes, returnType);
+    }
+    
+    
+    /**
+     * Converts a type (a Class) to its VM descriptor representation.<P>
+     * 
+     * Example:<BR>
+     * typeToDescriptor(Object.class) = "Ljava/lang/Object;" <BR>
+     * typeToDescriptor(boolean.class) = "Z"
+     * <P>
+     * Note the invariant typeToDescriptor(descriptorToType(desc)) == desc.
+     * 
+     * @see #descriptorToType
+     */
+    public static String typeToDescriptor (Class type)
+    {
+        return new signatureCompiler ().typeDescriptor (type);
+    }
+    
+    
+    /**
+     * Converts a VM descriptor to the corresponding type.<P>
+     * 
+     * Example:<BR>
+     * descriptorToType("[[I") = int[][].class <BR>
+     * descriptorToType("B") = byte.class
+     * <P>
+     * Note the invariant descriptorToType(typeToDescriptor(c)) == c.
+     * 
+     * @see #descriptorToType
+     */
+    public static Class descriptorToType (String typedescriptor) throws ClassNotFoundException
+    {
+        return new typeDescriptorCompiler ().descriptorToClass (typedescriptor);
+    }
+    
+    
+    
+    public static String descriptorToReturnType (String methoddescriptor)
+    {
+        final int i1 = methoddescriptor.indexOf ('(');
+        final int i2 = methoddescriptor.lastIndexOf (')');
+        
+        if ((i1 < 0) || (i2 <= 0) || (i1 >= i2) || (i2 >= methoddescriptor.length () - 1))
+            throw new IllegalArgumentException ("malformed method descriptor: [" + methoddescriptor + "]");
+
+        return methoddescriptor.substring (i2 + 1);                                                                                                
+    }
+    
+    
+    public static String [] descriptorToParameterTypes (String methoddescriptor)
+    {
+        //System.out.println ("METHOD DESCRIPTOR: [" + methoddescriptor + "]");
+    
+        try
+        {
+            final methodDescriptorCompiler compiler = new methodDescriptorCompiler (methoddescriptor);
+            compiler.methodDescriptor ();
+            return compiler.getResult ();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException ("error parsing [" + methoddescriptor + "]: " + e.toString ());
+        }
+        
+        /*
+        final java.util.Vector _result = new java.util.Vector ();
+        final StringBuffer token = new StringBuffer ();
+
+        char c = '*';
+        int scan = 0;
+        
+        for (int state = 0; state != 4; )
+        {
+            try
+            {
+                switch (state)
+                {
+                case 0:
+                    c = methoddescriptor.charAt (scan++);
+                    if (c == '(')
+                        state = 1;
+                    else
+                        throw new IllegalArgumentException ("malformed method descriptor: [" + methoddescriptor + "]");
+                    break;
+                    
+                case 1:
+                    c = methoddescriptor.charAt (scan);
+                    switch (c)
+                    {
+                    case 'B':
+                    case 'C':
+                    case 'D':
+                    case 'F':
+                    case 'I':
+                    case 'J':
+                    case 'S':
+                    case 'Z':
+                        token.append (c);
+                        _result.addElement (token.toString ());
+                        token.setLength (0);
+                        scan++;
+                        break;
+                        
+                    case 'L':
+                        state = 2;
+                        token.append (c);
+                        scan++;
+                        break;
+                        
+                    case '[':
+                        state = 3;    
+                        token.append (c);
+                        scan++;
+                        break;
+                        
+                    case ')':
+                        if (token.length () > 0)
+                        {
+                            _result.addElement (token.toString ());
+                            token.setLength (0);
+                        }
+                        state = 4;
+                        break;
+                            
+                        
+                    default:
+                        throw new IllegalArgumentException ("[state = " + state + ", c = " + c + "] malformed method descriptor: [" + methoddescriptor + "]");
+                        
+                    } // end of nested switch
+                    break;
+                    
+                case 2:
+                    c = methoddescriptor.charAt (scan++);
+                    token.append (c);
+                    if (c == ';')
+                    {
+                        _result.addElement (token.toString ());
+                        token.setLength (0);
+                        state = 1;
+                    }
+                    break;
+                    
+                case 3:
+                    c = methoddescriptor.charAt (scan++);
+                    token.append (c);
+                    if (c != '[')
+                    {
+                        state = 1;
+                    }
+                    break;    
+                    
+                } // end of switch
+                
+                //System.out.println ("[state = " + state + ", c = " + c + "]");
+            }
+            catch (StringIndexOutOfBoundsException e)
+            {
+                throw new IllegalArgumentException ("malformed method descriptor: [" + methoddescriptor + "]");
+            }
+        }
+        
+        String [] result = new String [_result.size ()];
+        _result.copyInto (result);
+        
+        return result;
+        */
+    }
+    
+    
+    public static String signatureToMethodDescriptor (final String [] parameterTypeDescriptors, final String returnTypeDescriptor)
+    {
+        final StringBuffer result = new StringBuffer ("(");
+        
+        for (int p = 0; p < parameterTypeDescriptors.length; p++)
+        {
+            result.append (parameterTypeDescriptors [p]);
+        }
+        
+        result.append (')');
+        result.append (returnTypeDescriptor);
+        
+        return result.toString (); 
+    }
+    
+    
+    public static String typeDescriptorToUserName (final String typedescriptor)
+    {
+        return new typeDescriptorCompiler2 ().descriptorToClass (typedescriptor);
+    }
+    
+    public static String methodDescriptorToUserName (final String methoddescriptor)
+    {
+        final String [] parameterTypes = descriptorToParameterTypes (methoddescriptor);
+        
+        final StringBuffer result = new StringBuffer ("(");
+        
+        for (int p = 0; p < parameterTypes.length; p++)
+        {
+            //System.out.println ("DESCRIPTOR: [" + parameterTypes [p] + "]");
+            
+            if (p > 0) result.append (", ");
+            
+            final String typeUserName = typeDescriptorToUserName (parameterTypes [p]);
+            int lastDot = typeUserName.lastIndexOf ('.');
+            
+            if ((lastDot < 0) || ! "java.lang.".equals (typeUserName.substring (0, lastDot + 1)))
+                result.append (typeUserName);
+            else
+                result.append (typeUserName.substring (lastDot + 1));
+        }
+        
+        result.append (')');
+        return result.toString (); 
+    }
+    
+    public static String fullMethodDescriptorToUserName (final String classJavaName, String methodName, final String methoddescriptor)
+    {
+        if ("<init>".equals (methodName))
+            methodName = simpleClassName (classJavaName);
+        if ("<clinit>".equals (methodName))
+            methodName = "<static class initializer>";
+        
+        return methodName + ' ' + methodDescriptorToUserName (methoddescriptor);
+    }
+    
+    // TODO: added most recently
+    public static String fullMethodDescriptorToFullUserName (final String classJavaName, String methodName, final String methoddescriptor)
+    {
+        if ("<init>".equals (methodName))
+            methodName = simpleClassName (classJavaName);
+        if ("<clinit>".equals (methodName))
+            methodName = "<static class initializer>";
+        
+        return classJavaName + '.' + methodName + ' ' + methodDescriptorToUserName (methoddescriptor);
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+    
+    private static String simpleClassName (final String classJavaName)
+    {
+        int lastDot = classJavaName.lastIndexOf ('.');
+        
+        if (lastDot < 0)
+            return classJavaName;
+        else
+            return classJavaName.substring (lastDot + 1);
+    }
+                                     
+    
+    
+    private static final class signatureCompiler
+    {
+        String signatureDescriptor (Class [] _parameterTypes, Class _returnType)
+        {
+            emit ('(');    parameterTypes (_parameterTypes); emit (')'); returnType (_returnType);
+            
+            return m_desc.toString ();
+        }
+        
+        String typeDescriptor (Class type)
+        {
+            parameterType (type);
+            
+            return m_desc.toString ();
+        }
+        
+        
+        private void parameterTypes (Class [] _parameterTypes)
+        {
+            if (_parameterTypes != null)
+            {
+                for (int p = 0; p < _parameterTypes.length; p++)
+                {
+                    parameterType (_parameterTypes [p]);
+                }
+            }
+        }
+        
+        
+        private void returnType (Class _returnType)
+        {
+            if ((_returnType == null) || (_returnType == Void.TYPE))
+                emit ('V');
+            else
+                parameterType (_returnType);
+        }
+        
+        
+        private void parameterType (Class _parameterType)
+        {
+            if (_parameterType != null)
+            {
+                if (_parameterType.isPrimitive ()) // base type:
+                {
+                    if (byte.class == _parameterType)            emit ('B');
+                    else if (char.class == _parameterType)        emit ('C');
+                    else if (double.class == _parameterType)    emit ('D');
+                    else if (float.class == _parameterType)        emit ('F');
+                    else if (int.class == _parameterType)        emit ('I');
+                    else if (long.class == _parameterType)        emit ('J');
+                    else if (short.class == _parameterType)        emit ('S');
+                    else if (boolean.class == _parameterType)    emit ('Z');
+                }
+                else if (_parameterType.isArray ()) // array type:
+                {
+                    emit ('[');    parameterType (_parameterType.getComponentType ());
+                }
+                else // object type:
+                {
+                    emit ('L');    emit (javaNameToVMName (_parameterType.getName ())); emit (';');
+                }
+            }
+        }
+        
+        
+        private void emit (String s)
+        {
+            m_desc.append (s);
+        }
+        
+        private void emit (char c)
+        {
+            m_desc.append (c);
+        }
+        
+        
+        private StringBuffer m_desc = new StringBuffer ();
+        
+    } // end of static class
+    
+    
+    
+    private static class typeDescriptorCompiler
+    {
+        /*
+        NOTE: the following would be a very simple solution to this problem
+        
+            Class.forName ('[' + descriptor).getComponentType ();
+        
+        except it only works in MS VM.
+        */
+        
+        Class descriptorToClass (String typedescriptor) throws ClassNotFoundException
+        {
+            char first = typedescriptor.charAt (0);
+            
+            if (first == '[')
+                // array type:
+                return arrayOf (typedescriptor.substring (1));
+            else if (first == 'L')
+                // object type:
+                return Class.forName (vmNameToJavaName (typedescriptor.substring (1, typedescriptor.length() - 1)));
+            else // primitive type
+            {
+                return primitive (first);
+            }
+        }
+        
+        
+        Class arrayOf (String typedescriptor) throws ClassNotFoundException
+        {
+            char first = typedescriptor.charAt (0);
+            Class component;
+            
+            if (first == '[')
+                // array type:
+                component = arrayOf (typedescriptor.substring (1));
+            else if (first == 'L')
+                // object type:
+                component =  Class.forName (vmNameToJavaName (typedescriptor.substring (1, typedescriptor.length() - 1)));
+            else // primitive type
+            {
+                component = primitive (first);
+            }
+            
+            Object array = Array.newInstance (component, 0);
+            return array.getClass ();
+        }
+        
+        
+        Class primitive (char c) throws ClassNotFoundException
+        {
+            if (c == 'B') return byte.class;
+            else if (c == 'C') return char.class;
+            else if (c == 'D') return double.class;
+            else if (c == 'F') return float.class;
+            else if (c == 'I') return int.class;
+            else if (c == 'J') return long.class;
+            else if (c == 'S') return short.class;
+            else if (c == 'Z') return boolean.class;
+            else throw new ClassNotFoundException ("unknown base type: " + c);
+        }
+        
+    } // end of static class
+
+    
+    private static class typeDescriptorCompiler2
+    {
+        String descriptorToClass (String typedescriptor)
+        {
+            //System.out.println ("typedesc1 -> " + typedescriptor);
+            
+            char first = typedescriptor.charAt (0);
+            
+            if (first == '[')
+                // array type:
+                return arrayOf (typedescriptor.substring (1));
+            else if (first == 'L')
+                // object type:
+                return vmNameToJavaName (typedescriptor.substring (1, typedescriptor.length() - 1));
+            else // primitive type
+                return primitive (first);
+        }
+        
+        
+        String arrayOf (String typedescriptor)
+        {
+            //System.out.println ("typedesc2 -> " + typedescriptor);
+            
+            char first = typedescriptor.charAt (0);
+            String component;
+            
+            if (first == '[')
+                // array type:
+                component = arrayOf (typedescriptor.substring (1));
+            else if (first == 'L')
+                // object type:
+                component = vmNameToJavaName (typedescriptor.substring (1, typedescriptor.length() - 1));
+            else // primitive type
+                component = primitive (first);
+            
+            String array = component + " []";
+            return array;
+        }
+        
+        
+        String primitive (char c)
+        {
+            switch (c)
+            {
+            case 'B': return "byte";
+            case 'C': return "char";
+            case 'D': return "double";
+            case 'F': return "float";
+            case 'I': return "int";
+            case 'J': return "long";
+            case 'S': return "short";
+            case 'Z': return "boolean";
+            default:          
+                throw new IllegalArgumentException ("unknown primitive: " + c);
+            }
+        }
+        
+    } // end of static class
+
+    
+    private static class methodDescriptorCompiler
+    {
+        methodDescriptorCompiler (String methoddescriptor)
+        {
+            m_in = new java.io.PushbackReader (new java.io.StringReader (methoddescriptor));
+        }
+        
+        String [] getResult ()
+        {
+            final String [] result = new String [m_result.size ()];
+            m_result.toArray (result);
+            
+            return result;
+        }
+        
+        void methodDescriptor () throws IOException
+        {
+            consume ('(');
+            
+            char c;
+            while ((c = (char) m_in.read ()) != ')')
+            {
+                m_in.unread (c);
+                parameterDescriptor ();
+            }
+            returnDescriptor ();
+        }
+        
+        void parameterDescriptor () throws IOException
+        {
+            fieldType ();
+            newToken ();
+        }
+        
+        void returnDescriptor () throws IOException
+        {
+            char c = (char) m_in.read ();
+            
+            switch (c)
+            {
+            case 'V':
+                m_token.append (c);
+                break;
+                
+            default:
+                m_in.unread (c);
+                fieldType ();
+                
+            }
+            // ignore return type for now: newToken ();
+        }
+        
+        void componentType () throws IOException
+        {
+            fieldType ();
+        }
+        
+        void objectType () throws IOException
+        {
+            consume ('L');
+            m_token.append ('L');
+            
+            char c;
+            while ((c = (char) m_in.read ()) != ';')
+            {
+                m_token.append (c);
+            }
+            m_token.append (';');
+        }
+        
+        void arrayType () throws IOException
+        {
+            consume ('[');
+            m_token.append ('[');
+        
+            componentType ();
+        }
+        
+        void fieldType () throws IOException
+        {
+            char c = (char) m_in.read ();
+            m_in.unread (c);
+            
+            switch (c)
+            {
+            case 'L':
+                objectType ();
+                break;
+                
+            case '[':
+                arrayType ();
+                break;
+                
+            default:
+                baseType ();
+                break;
+            }
+        }
+
+        
+        void baseType () throws IOException
+        {
+            char c = (char) m_in.read ();
+            
+            switch (c)
+            {
+            case 'B': 
+            case 'C': 
+            case 'D':
+            case 'F':
+            case 'I':
+            case 'J':
+            case 'S':
+            case 'Z':
+                m_token.append (c);
+                break;
+                
+            default:          
+                throw new IllegalArgumentException ("unknown base type: " + c);
+            }
+        }
+        
+        
+        private void consume (char expected) throws IOException
+        {
+            char c = (char) m_in.read ();
+            
+            if (c != expected)
+                throw new IllegalArgumentException ("consumed '" + c + "' while expecting '" + expected + "'");
+        }
+        
+        
+        
+        private void newToken ()
+        {
+            //System.out.println ("NEW TOKEN [" + m_token.toString () + "]");
+            
+            m_result.add (m_token.toString ());
+            m_token.setLength (0);
+        }
+    
+        final java.util.List m_result = new java.util.ArrayList ();
+        private StringBuffer m_token = new StringBuffer ();
+        private java.io.PushbackReader m_in;
+    } // end of nested class
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/lib/UDataInputStream.java b/core/java12/com/vladium/jcd/lib/UDataInputStream.java
new file mode 100644
index 0000000..e43d9f8
--- /dev/null
+++ b/core/java12/com/vladium/jcd/lib/UDataInputStream.java
@@ -0,0 +1,58 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: UDataInputStream.java,v 1.1.1.1.2.1 2004/07/10 03:34:53 vlad_r Exp $
+ */
+package com.vladium.jcd.lib;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * A trivial extension to java.io.DataInputStream to provide methods for
+ * reading unsigned 16- and 32-bit integers with simple mnemonics. It uses
+ * correspondingly wider native types to preserve the full range of the unsigned
+ * types.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class UDataInputStream extends DataInputStream
+{
+    // public: ................................................................
+
+    
+    public UDataInputStream (final InputStream _in)
+    {
+        super (_in);
+    }
+
+    
+    public final int readU2 () throws IOException
+    {
+        final short value = readShort ();
+        
+        return ((int) value) & 0xFFFF; // widening cast sign-extends
+    }
+    
+    
+    public final long readU4 () throws IOException
+    {
+        final int value = readInt ();
+        
+        return ((long) value) & 0xFFFFFFFFL; // widening cast sign-extends
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/lib/UDataOutputStream.java b/core/java12/com/vladium/jcd/lib/UDataOutputStream.java
new file mode 100644
index 0000000..0fb9f0b
--- /dev/null
+++ b/core/java12/com/vladium/jcd/lib/UDataOutputStream.java
@@ -0,0 +1,54 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: UDataOutputStream.java,v 1.1.1.1.2.1 2004/07/10 03:34:53 vlad_r Exp $
+ */
+package com.vladium.jcd.lib;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * A trivial extension to java.io.DataInputStream to provide methods for
+ * writing unsigned 16- and 32-bit integers with simple mnemonics. It uses
+ * correspondingly wider native types to preserve the full range of the unsigned
+ * types.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class UDataOutputStream extends DataOutputStream
+{
+    // public: ................................................................
+
+    
+    public UDataOutputStream (final OutputStream _out)
+    {
+        super (_out);
+    }
+    
+    
+    public final void writeU2 (final int uint) throws IOException
+    {
+        writeShort ((short) uint); // this narrowing cast is Ok
+    }
+    
+    
+    public final void writeU4 (final long ulong) throws IOException
+    {
+        writeInt ((int) ulong); // this narrowing cast is Ok
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/opcodes/IOpcodeVisitor.java b/core/java12/com/vladium/jcd/opcodes/IOpcodeVisitor.java
new file mode 100644
index 0000000..f4fee3c
--- /dev/null
+++ b/core/java12/com/vladium/jcd/opcodes/IOpcodeVisitor.java
@@ -0,0 +1,23 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IOpcodeVisitor.java,v 1.1.1.1 2004/05/09 16:57:51 vlad_r Exp $
+ */
+package com.vladium.jcd.opcodes;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IOpcodeVisitor
+{
+    // public: ................................................................
+    
+    void visit (int opcode, boolean wide, int offset, Object ctx);
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/jcd/opcodes/IOpcodes.java b/core/java12/com/vladium/jcd/opcodes/IOpcodes.java
new file mode 100644
index 0000000..2927b10
--- /dev/null
+++ b/core/java12/com/vladium/jcd/opcodes/IOpcodes.java
@@ -0,0 +1,607 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IOpcodes.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.jcd.opcodes;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IOpcodes
+{
+    // public: ................................................................
+        
+    //  opcode              hex     dec opbytes stackwords wideable    
+    int _nop              = 0x00; // 00     0   0
+    int _aconst_null      = 0x01; // 01     0   +1
+    int _iconst_m1        = 0x02; // 02     0   +1
+    int _iconst_0         = 0x03; // 03     0   +1
+    int _iconst_1         = 0x04; // 04     0   +1
+    int _iconst_2         = 0x05; // 05     0   +1
+    int _iconst_3         = 0x06; // 06     0   +1
+    int _iconst_4         = 0x07; // 07     0   +1
+    int _iconst_5         = 0x08; // 08     0   +1
+    int _lconst_0         = 0x09; // 09     0   +2
+    int _lconst_1         = 0x0A; // 10     0   +2
+    int _fconst_0         = 0x0B; // 11     0   +1
+    int _fconst_1         = 0x0C; // 12     0   +1
+    int _fconst_2         = 0x0D; // 13     0   +1
+    int _dconst_0         = 0x0E; // 14     0   +2
+    int _dconst_1         = 0x0F; // 15     0   +2
+    int _bipush           = 0x10; // 16     1   +1
+    int _sipush           = 0x11; // 17     2   +1
+    int _ldc              = 0x12; // 18     1   +1
+    int _ldc_w            = 0x13; // 19     2   +1
+    int _ldc2_w           = 0x14; // 20     2   +2
+    int _iload            = 0x15; // 21     1   +1  true
+    int _lload            = 0x16; // 22     1   +2  true
+    int _fload            = 0x17; // 23     1   +1  true
+    int _dload            = 0x18; // 24     1   +2  true
+    int _aload            = 0x19; // 25     1   +1  true  
+    int _iload_0          = 0x1A; // 26     0   +1
+    int _iload_1          = 0x1B; // 27     0   +1
+    int _iload_2          = 0x1C; // 28     0   +1
+    int _iload_3          = 0x1D; // 29     0   +1
+    int _lload_0          = 0x1E; // 30     0   +2
+    int _lload_1          = 0x1F; // 31     0   +2
+    int _lload_2          = 0x20; // 32     0   +2
+    int _lload_3          = 0x21; // 33     0   +2
+    int _fload_0          = 0x22; // 34     0   +1
+    int _fload_1          = 0x23; // 35     0   +1
+    int _fload_2          = 0x24; // 36     0   +1
+    int _fload_3          = 0x25; // 37     0   +1
+    int _dload_0          = 0x26; // 38     0   +2
+    int _dload_1          = 0x27; // 39     0   +2
+    int _dload_2          = 0x28; // 40     0   +2
+    int _dload_3          = 0x29; // 41     0   +2
+    int _aload_0          = 0x2A; // 42     0   +1
+    int _aload_1          = 0x2B; // 43     0   +1
+    int _aload_2          = 0x2C; // 44     0   +1
+    int _aload_3          = 0x2D; // 45     0   +1
+    int _iaload           = 0x2E; // 46     0   -1
+    int _laload           = 0x2F; // 47     0   0
+    int _faload           = 0x30; // 48     0   -1
+    int _daload           = 0x31; // 49     0   0
+    int _aaload           = 0x32; // 50     0   -1
+    int _baload           = 0x33; // 51     0   -1
+    int _caload           = 0x34; // 52     0   -1
+    int _saload           = 0x35; // 53     0   -1
+    int _istore           = 0x36; // 54     1   -1  true
+    int _lstore           = 0x37; // 55     1   -2  true
+    int _fstore           = 0x38; // 56     1   -1  true
+    int _dstore           = 0x39; // 57     1   -2  true
+    int _astore           = 0x3A; // 58     1   -1  true
+    int _istore_0         = 0x3B; // 59     0   -1
+    int _istore_1         = 0x3C; // 60     0   -1
+    int _istore_2         = 0x3D; // 61     0   -1
+    int _istore_3         = 0x3E; // 62     0   -1
+    int _lstore_0         = 0x3F; // 63     0   -2
+    int _lstore_1         = 0x40; // 64     0   -2
+    int _lstore_2         = 0x41; // 65     0   -2
+    int _lstore_3         = 0x42; // 66     0   -2
+    int _fstore_0         = 0x43; // 67     0   -1
+    int _fstore_1         = 0x44; // 68     0   -1
+    int _fstore_2         = 0x45; // 69     0   -1
+    int _fstore_3         = 0x46; // 70     0   -1
+    int _dstore_0         = 0x47; // 71     0   -2
+    int _dstore_1         = 0x48; // 72     0   -2
+    int _dstore_2         = 0x49; // 73     0   -2
+    int _dstore_3         = 0x4A; // 74     0   -2
+    int _astore_0         = 0x4B; // 75     0   -1
+    int _astore_1         = 0x4C; // 76     0   -1
+    int _astore_2         = 0x4D; // 77     0   -1
+    int _astore_3         = 0x4E; // 78     0   -1
+    int _iastore          = 0x4F; // 79     0   -3
+    int _lastore          = 0x50; // 80     0   -4
+    int _fastore          = 0x51; // 81     0   -3
+    int _dastore          = 0x52; // 82     0   -4
+    int _aastore          = 0x53; // 83     0   -3
+    int _bastore          = 0x54; // 84     0   -3
+    int _castore          = 0x55; // 85     0   -3
+    int _sastore          = 0x56; // 86     0   -3
+    int _pop              = 0x57; // 87     0   -1
+    int _pop2             = 0x58; // 88     0   -2
+    int _dup              = 0x59; // 89     0   +1
+    int _dup_x1           = 0x5A; // 90     0   +1
+    int _dup_x2           = 0x5B; // 91     0   +1
+    int _dup2             = 0x5C; // 92     0   +2
+    int _dup2_x1          = 0x5D; // 93     0   +2
+    int _dup2_x2          = 0x5E; // 94     0   +2
+    int _swap             = 0x5F; // 95     0   0
+    int _iadd             = 0x60; // 96     0   -1
+    int _ladd             = 0x61; // 97     0   -2
+    int _fadd             = 0x62; // 98     0   -1
+    int _dadd             = 0x63; // 99     0   -2
+    int _isub             = 0x64; // 100    0   -1
+    int _lsub             = 0x65; // 101    0   -2
+    int _fsub             = 0x66; // 102    0   -1
+    int _dsub             = 0x67; // 103    0   -2
+    int _imul             = 0x68; // 104    0   -1
+    int _lmul             = 0x69; // 105    0   -2
+    int _fmul             = 0x6A; // 106    0   -1
+    int _dmul             = 0x6B; // 107    0   -2
+    int _idiv             = 0x6C; // 108    0   -1
+    int _ldiv             = 0x6D; // 109    0   -2
+    int _fdiv             = 0x6E; // 110    0   -1
+    int _ddiv             = 0x6F; // 111    0   -2
+    int _irem             = 0x70; // 112    0   -1
+    int _lrem             = 0x71; // 113    0   -2
+    int _frem             = 0x72; // 114    0   -1
+    int _drem             = 0x73; // 115    0   -2
+    int _ineg             = 0x74; // 116    0   0
+    int _lneg             = 0x75; // 117    0   0
+    int _fneg             = 0x76; // 118    0   0
+    int _dneg             = 0x77; // 119    0   0
+    int _ishl             = 0x78; // 120    0   -1
+    int _lshl             = 0x79; // 121    0   -1
+    int _ishr             = 0x7A; // 122    0   -1
+    int _lshr             = 0x7B; // 123    0   -1
+    int _iushr            = 0x7C; // 124    0   -1
+    int _lushr            = 0x7D; // 125    0   -2
+    int _iand             = 0x7E; // 126    0   -1
+    int _land             = 0x7F; // 127    0   -2
+    int _ior              = 0x80; // 128    0   -1
+    int _lor              = 0x81; // 129    0   -2
+    int _ixor             = 0x82; // 130    0   -1
+    int _lxor             = 0x83; // 131    0   -2
+    int _iinc             = 0x84; // 132    2   0   true    [widening is tricky here]
+    int _i2l              = 0x85; // 133    0   +1
+    int _i2f              = 0x86; // 134    0   0
+    int _i2d              = 0x87; // 135    0   +1
+    int _l2i              = 0x88; // 136    0   -1
+    int _l2f              = 0x89; // 137    0   -1
+    int _l2d              = 0x8A; // 138    0   0
+    int _f2i              = 0x8B; // 139    0   0
+    int _f2l              = 0x8C; // 140    0   +1
+    int _f2d              = 0x8D; // 141    0   +1
+    int _d2i              = 0x8E; // 142    0   -1
+    int _d2l              = 0x8F; // 143    0   0
+    int _d2f              = 0x90; // 144    0   -1
+    int _i2b              = 0x91; // 145    0   0
+    int _i2c              = 0x92; // 146    0   0
+    int _i2s              = 0x93; // 147    0   0
+    int _lcmp             = 0x94; // 148    0   -3
+    int _fcmpl            = 0x95; // 149    0   -1
+    int _fcmpg            = 0x96; // 150    0   -1
+    int _dcmpl            = 0x97; // 151    0   -3
+    int _dcmpg            = 0x98; // 152    0   -3
+    int _ifeq             = 0x99; // 153    2   -1
+    int _ifne             = 0x9A; // 154    2   -1
+    int _iflt             = 0x9B; // 155    2   -1
+    int _ifge             = 0x9C; // 156    2   -1
+    int _ifgt             = 0x9D; // 157    2   -1
+    int _ifle             = 0x9E; // 158    2   -1
+    int _if_icmpeq        = 0x9F; // 159    2   -2
+    int _if_icmpne        = 0xA0; // 160    2   -2
+    int _if_icmplt        = 0xA1; // 161    2   -2
+    int _if_icmpge        = 0xA2; // 162    2   -2
+    int _if_icmpgt        = 0xA3; // 163    2   -2
+    int _if_icmple        = 0xA4; // 164    2   -2
+    int _if_acmpeq        = 0xA5; // 165    2   -2
+    int _if_acmpne        = 0xA6; // 166    2   -2
+    int _goto             = 0xA7; // 167    2   0
+    int _jsr              = 0xA8; // 168    2   +1
+    int _ret              = 0xA9; // 169    1   0   true
+    int _tableswitch      = 0xAA; // 170    *   -1      [there are padding bytes and variable number of operands]
+    int _lookupswitch     = 0xAB; // 171    *   -1      [there are padding bytes and variable number of operands]
+    int _ireturn          = 0xAC; // 172    0   -1*     [current method returns]
+    int _lreturn          = 0xAD; // 173    0   -2*     [current method returns]
+    int _freturn          = 0xAE; // 174    0   -1*     [current method returns]
+    int _dreturn          = 0xAF; // 175    0   -2*     [current method returns]
+    int _areturn          = 0xB0; // 176    0   -1*     [current method returns]
+    int _return           = 0xB1; // 177    0   0*      [current method returns]
+    int _getstatic        = 0xB2; // 178    2   +1 or +2*   [after stack depends on the field type]
+    int _putstatic        = 0xB3; // 179    2   -1 or -2*   [after stack depends on the field type]
+    int _getfield         = 0xB4; // 180    2   0 or +1*    [after stack depends on the field type]
+    int _putfield         = 0xB5; // 181    2   -2 or -3*   [after stack depends on the field type]
+    int _invokevirtual    = 0xB6; // 182    2   *   *   [stack words pushed for the call are emptied]
+    int _invokespecial    = 0xB7; // 183    2   *   *   [stack words pushed for the call are emptied]
+    int _invokestatic     = 0xB8; // 184    2   *   *   [stack words pushed for the call are emptied]
+    int _invokeinterface  = 0xB9; // 185    4   *   *   [last operand is 0; stack words pushed for the call are emptied]
+    int _unused           = 0xBA; // 186    *   *   *   [for historical reasons, opcode value 186 is not used]
+    int _new              = 0xBB; // 187    2   +1
+    int _newarray         = 0xBC; // 188    1   0
+    int _anewarray        = 0xBD; // 189    2   0
+    int _arraylength      = 0xBE; // 190    0   0
+    int _athrow           = 0xBF; // 191    0   0*  *   [stack frame is emptied except for 1 obj ref]
+    int _checkcast        = 0xC0; // 192    2   0
+    int _instanceof       = 0xC1; // 193    2   0
+    int _monitorenter     = 0xC2; // 194    0   -1
+    int _monitorexit      = 0xC3; // 195    0   -1
+    int _wide             = 0xC4; // 196    *   *       [depends on instruction being modified]
+    int _multianewarray   = 0xC5; // 197    3   *       [variable number of stack operands]
+    int _ifnull           = 0xC6; // 198    2   -1
+    int _ifnonnull        = 0xC7; // 199    2   -1
+    int _goto_w           = 0xC8; // 200    4   0
+    int _jsr_w            = 0xC9; // 201    4   +1
+    // reserved opcodes:
+    int _breakpoint       = 0xCA; // 202
+    int _impdep1          = 0xFE; // 254
+    int _impdep2          = 0xFF; // 255
+
+    
+    String [] MNEMONICS =
+    {
+        "nop",              // 0x00    00
+        "aconst_null",      // 0x01    01
+        "iconst_m1",        // 0x02    02
+        "iconst_0",         // 0x03    03
+        "iconst_1",         // 0x04    04
+        "iconst_2",         // 0x05    05
+        "iconst_3",         // 0x06    06
+        "iconst_4",         // 0x07    07
+        "iconst_5",         // 0x08    08
+        "lconst_0",         // 0x09    09
+        "lconst_1",         // 0x0A    10
+        "fconst_0",         // 0x0B    11
+        "fconst_1",         // 0x0C    12
+        "fconst_2",         // 0x0D    13
+        "dconst_0",         // 0x0E    14
+        "dconst_1",         // 0x0F    15
+        "bipush",           // 0x10    16
+        "sipush",           // 0x11    17
+        "ldc",              // 0x12    18
+        "ldc_w",            // 0x13    19
+        "ldc2_w",           // 0x14    20
+        "iload",            // 0x15    21
+        "lload",            // 0x16    22
+        "fload",            // 0x17    23
+        "dload",            // 0x18    24
+        "aload",            // 0x19    25
+        "iload_0",          // 0x1A    26
+        "iload_1",          // 0x1B    27
+        "iload_2",          // 0x1C    28
+        "iload_3",          // 0x1D    29
+        "lload_0",          // 0x1E    30
+        "lload_1",          // 0x1F    31
+        "lload_2",          // 0x20    32
+        "lload_3",          // 0x21    33
+        "fload_0",          // 0x22    34
+        "fload_1",          // 0x23    35
+        "fload_2",          // 0x24    36
+        "fload_3",          // 0x25    37
+        "dload_0",          // 0x26    38
+        "dload_1",          // 0x27    39
+        "dload_2",          // 0x28    40
+        "dload_3",          // 0x29    41
+        "aload_0",          // 0x2A    42
+        "aload_1",          // 0x2B    43
+        "aload_2",          // 0x2C    44
+        "aload_3",          // 0x2D    45
+        "iaload",           // 0x2E    46
+        "laload",           // 0x2F    47
+        "faload",           // 0x30    48
+        "daload",           // 0x31    49
+        "aaload",           // 0x32    50
+        "baload",           // 0x33    51
+        "caload",           // 0x34    52
+        "saload",           // 0x35    53
+        "istore",           // 0x36    54
+        "lstore",           // 0x37    55
+        "fstore",           // 0x38    56
+        "dstore",           // 0x39    57
+        "astore",           // 0x3A    58
+        "istore_0",         // 0x3B    59
+        "istore_1",         // 0x3C    60
+        "istore_2",         // 0x3D    61
+        "istore_3",         // 0x3E    62
+        "lstore_0",         // 0x3F    63
+        "lstore_1",         // 0x40    64
+        "lstore_2",         // 0x41    65
+        "lstore_3",         // 0x42    66
+        "fstore_0",         // 0x43    67
+        "fstore_1",         // 0x44    68
+        "fstore_2",         // 0x45    69
+        "fstore_3",         // 0x46    70
+        "dstore_0",         // 0x47    71
+        "dstore_1",         // 0x48    72
+        "dstore_2",         // 0x49    73
+        "dstore_3",         // 0x4A    74
+        "astore_0",         // 0x4B    75
+        "astore_1",         // 0x4C    76
+        "astore_2",         // 0x4D    77
+        "astore_3",         // 0x4E    78
+        "iastore",          // 0x4F    79
+        "lastore",          // 0x50    80
+        "fastore",          // 0x51    81
+        "dastore",          // 0x52    82
+        "aastore",          // 0x53    83
+        "bastore",          // 0x54    84
+        "castore",          // 0x55    85
+        "sastore",          // 0x56    86
+        "pop",              // 0x57    87
+        "pop2",             // 0x58    88
+        "dup",              // 0x59    089
+        "dup_x1",           // 0x5A    090
+        "dup_x2",           // 0x5B    091
+        "dup2",             // 0x5C    092
+        "dup2_x1",          // 0x5D    093
+        "dup2_x2",          // 0x5E    094
+        "swap",             // 0x5F    095
+        "iadd",             // 0x60    096
+        "ladd",             // 0x61    097
+        "fadd",             // 0x62    098
+        "dadd",             // 0x63    099
+        "isub",             // 0x64    100
+        "lsub",             // 0x65    101
+        "fsub",             // 0x66    102
+        "dsub",             // 0x67    103
+        "imul",             // 0x68    104
+        "lmul",             // 0x69    105
+        "fmul",             // 0x6A    106
+        "dmul",             // 0x6B    107
+        "idiv",             // 0x6C    108
+        "ldiv",             // 0x6D    109
+        "fdiv",             // 0x6E    110
+        "ddiv",             // 0x6F    111
+        "irem",             // 0x70    112
+        "lrem",             // 0x71    113
+        "frem",             // 0x72    114
+        "drem",             // 0x73    115
+        "ineg",             // 0x74    116
+        "lneg",             // 0x75    117
+        "fneg",             // 0x76    118
+        "dneg",             // 0x77    119
+        "ishl",             // 0x78    120
+        "lshl",             // 0x79    121
+        "ishr",             // 0x7A    122
+        "lshr",             // 0x7B    123
+        "iushr",            // 0x7C    124
+        "lushr",            // 0x7D    125
+        "iand",             // 0x7E    126
+        "land",             // 0x7F    127
+        "ior",              // 0x80    128
+        "lor",              // 0x81    129
+        "ixor",             // 0x82    130
+        "lxor",             // 0x83    131
+        "iinc",             // 0x84    132
+        "i2l",              // 0x85    133
+        "i2f",              // 0x86    134
+        "i2d",              // 0x87    135
+        "l2i",              // 0x88    136
+        "l2f",              // 0x89    137
+        "l2d",              // 0x8A    138
+        "f2i",              // 0x8B    139
+        "f2l",              // 0x8C    140
+        "f2d",              // 0x8D    141
+        "d2i",              // 0x8E    142
+        "d2l",              // 0x8F    143
+        "d2f",              // 0x90    144
+        "i2b",              // 0x91    145
+        "i2c",              // 0x92    146
+        "i2s",              // 0x93    147
+        "lcmp",             // 0x94    148
+        "fcmpl",            // 0x95    149
+        "fcmpg",            // 0x96    150
+        "dcmpl",            // 0x97    151
+        "dcmpg",            // 0x98    152
+        "ifeq",             // 0x99    153
+        "ifne",             // 0x9A    154
+        "iflt",             // 0x9B    155
+        "ifge",             // 0x9C    156
+        "ifgt",             // 0x9D    157
+        "ifle",             // 0x9E    158
+        "if_icmpeq",        // 0x9F    159
+        "if_icmpne",        // 0xA0    160
+        "if_icmplt",        // 0xA1    161
+        "if_icmpge",        // 0xA2    162
+        "if_icmpgt",        // 0xA3    163
+        "if_icmple",        // 0xA4    164
+        "if_acmpeq",        // 0xA5    165
+        "if_acmpne",        // 0xA6    166
+        "goto",             // 0xA7    167
+        "jsr",              // 0xA8    168
+        "ret",              // 0xA9    169
+        "tableswitch",      // 0xAA    170
+        "lookupswitch",     // 0xAB    171
+        "ireturn",          // 0xAC    172
+        "lreturn",          // 0xAD    173
+        "freturn",          // 0xAE    174
+        "dreturn",          // 0xAF    175
+        "areturn",          // 0xB0    176
+        "return",           // 0xB1    177
+        "getstatic",        // 0xB2    178
+        "putstatic",        // 0xB3    179
+        "getfield",         // 0xB4    180
+        "putfield",         // 0xB5    181
+        "invokevirtual",    // 0xB6    182
+        "invokespecial",    // 0xB7    183
+        "invokestatic",     // 0xB8    184
+        "invokeinterface",  // 0xB9    185
+        "unused",           // 0xBA    186
+        "new",              // 0xBB    187
+        "newarray",         // 0xBC    188
+        "anewarray",        // 0xBD    189
+        "arraylength",      // 0xBE    190
+        "athrow",           // 0xBF    191
+        "checkcast",        // 0xC0    192
+        "instanceof",       // 0xC1    193
+        "monitorenter",     // 0xC2    194
+        "monitorexit",      // 0xC3    195
+        "[wide]",           // 0xC4    196
+        "multianewarray",   // 0xC5    197
+        "ifnull",           // 0xC6    198
+        "ifnonnull",        // 0xC7    199
+        "goto_w",           // 0xC8    200
+        "jsr_w"             // 0xC9    201
+    };
+    
+    
+    boolean [] CONDITIONAL_BRANCHES = clinit._CONDITIONAL_BRANCHES;
+    boolean [] COMPOUND_CONDITIONAL_BRANCHES = clinit._COMPOUND_CONDITIONAL_BRANCHES;
+    boolean [] UNCONDITIONAL_BRANCHES = clinit._UNCONDITIONAL_BRANCHES;
+    boolean [] BRANCHES = clinit._BRANCHES;
+    
+    int [] NARROW_SIZE = clinit._NARROW_SIZE; // including the opcode itself
+    int [] WIDE_SIZE = clinit._WIDE_SIZE; // including the opcode itself
+    
+    
+    static final class clinit
+    {
+        static final boolean [] _CONDITIONAL_BRANCHES;
+        static final boolean [] _COMPOUND_CONDITIONAL_BRANCHES;
+        static final boolean [] _UNCONDITIONAL_BRANCHES;
+        static final boolean [] _BRANCHES;
+        static final int [] _NARROW_SIZE;
+        static final int [] _WIDE_SIZE;
+        
+        static
+        {
+            final int opcodeCount = MNEMONICS.length;
+            
+            _CONDITIONAL_BRANCHES = new boolean [opcodeCount];
+            
+            _CONDITIONAL_BRANCHES [_ifeq] = true;
+            _CONDITIONAL_BRANCHES [_iflt] = true;
+            _CONDITIONAL_BRANCHES [_ifle] = true;
+            _CONDITIONAL_BRANCHES [_ifne] = true;
+            _CONDITIONAL_BRANCHES [_ifgt] = true;
+            _CONDITIONAL_BRANCHES [_ifge] = true;
+            _CONDITIONAL_BRANCHES [_ifnull] = true;
+            _CONDITIONAL_BRANCHES [_ifnonnull] = true;
+            _CONDITIONAL_BRANCHES [_if_icmpeq] = true;
+            _CONDITIONAL_BRANCHES [_if_icmpne] = true;
+            _CONDITIONAL_BRANCHES [_if_icmplt] = true;
+            _CONDITIONAL_BRANCHES [_if_icmpgt] = true;
+            _CONDITIONAL_BRANCHES [_if_icmple] = true;
+            _CONDITIONAL_BRANCHES [_if_icmpge] = true;
+            _CONDITIONAL_BRANCHES [_if_acmpeq] = true;
+            _CONDITIONAL_BRANCHES [_if_acmpne] = true;
+
+
+            _COMPOUND_CONDITIONAL_BRANCHES = new boolean [opcodeCount];
+            
+            _COMPOUND_CONDITIONAL_BRANCHES [_tableswitch] = true;
+            _COMPOUND_CONDITIONAL_BRANCHES [_lookupswitch] = true;
+
+            
+            _UNCONDITIONAL_BRANCHES = new boolean  [opcodeCount];
+            
+            _UNCONDITIONAL_BRANCHES [_goto] = true;
+            _UNCONDITIONAL_BRANCHES [_goto_w] = true;
+            _UNCONDITIONAL_BRANCHES [_jsr] = true;
+            _UNCONDITIONAL_BRANCHES [_jsr_w] = true;
+            _UNCONDITIONAL_BRANCHES [_ret] = true;
+
+            _UNCONDITIONAL_BRANCHES [_ireturn] = true;
+            _UNCONDITIONAL_BRANCHES [_lreturn] = true;
+            _UNCONDITIONAL_BRANCHES [_freturn] = true;
+            _UNCONDITIONAL_BRANCHES [_dreturn] = true;
+            _UNCONDITIONAL_BRANCHES [_areturn] = true;
+            _UNCONDITIONAL_BRANCHES [_return] = true;
+
+            _UNCONDITIONAL_BRANCHES [_athrow] = true;
+
+
+            _BRANCHES = new boolean [opcodeCount];
+            
+            for (int o = 0; o < opcodeCount; ++ o)
+                if (_CONDITIONAL_BRANCHES [o]) _BRANCHES [o] = true;
+
+            for (int o = 0; o < opcodeCount; ++ o)
+                if (_COMPOUND_CONDITIONAL_BRANCHES [o]) _BRANCHES [o] = true;
+
+            for (int o = 0; o < opcodeCount; ++ o)
+                if (_UNCONDITIONAL_BRANCHES [o]) _BRANCHES [o] = true;
+
+
+            _NARROW_SIZE = new int [opcodeCount];
+            
+            for (int o = 0; o < opcodeCount; ++ o) _NARROW_SIZE [o] = 1;
+            
+            _NARROW_SIZE [_bipush] = 2;
+            _NARROW_SIZE [_sipush] = 3;
+
+            _NARROW_SIZE [_ldc] = 2;
+            _NARROW_SIZE [_ldc_w] = 3;
+            _NARROW_SIZE [_ldc2_w] = 3;
+
+            _NARROW_SIZE [_iload] = 2;
+            _NARROW_SIZE [_lload] = 2;
+            _NARROW_SIZE [_fload] = 2;
+            _NARROW_SIZE [_dload] = 2;
+            _NARROW_SIZE [_aload] = 2;
+            _NARROW_SIZE [_istore] = 2;
+            _NARROW_SIZE [_lstore] = 2;
+            _NARROW_SIZE [_fstore] = 2;
+            _NARROW_SIZE [_dstore] = 2;
+            _NARROW_SIZE [_astore] = 2;
+
+            _NARROW_SIZE [_iinc] = 3;
+
+            _NARROW_SIZE [_ifeq] = 3;
+            _NARROW_SIZE [_ifne] = 3;
+            _NARROW_SIZE [_iflt] = 3;
+            _NARROW_SIZE [_ifge] = 3;
+            _NARROW_SIZE [_ifgt] = 3;
+            _NARROW_SIZE [_ifle] = 3;
+            _NARROW_SIZE [_if_icmpeq] = 3;
+            _NARROW_SIZE [_if_icmpne] = 3;
+            _NARROW_SIZE [_if_icmplt] = 3;
+            _NARROW_SIZE [_if_icmpge] = 3;
+            _NARROW_SIZE [_if_icmpgt] = 3;
+            _NARROW_SIZE [_if_icmple] = 3;
+            _NARROW_SIZE [_if_acmpeq] = 3;
+            _NARROW_SIZE [_if_acmpne] = 3;
+            _NARROW_SIZE [_goto] = 3;
+            _NARROW_SIZE [_jsr] = 3;
+            _NARROW_SIZE [_ifnull] = 3;
+            _NARROW_SIZE [_ifnonnull] = 3;
+
+            _NARROW_SIZE [_ret] = 2;
+
+            _NARROW_SIZE [_lookupswitch] = -1;   // special case #2
+            _NARROW_SIZE [_tableswitch] = 0;    // special case #1
+            
+            _NARROW_SIZE [_getstatic] = 3;
+            _NARROW_SIZE [_putstatic] = 3;
+            _NARROW_SIZE [_getfield] = 3;
+            _NARROW_SIZE [_putfield] = 3;
+
+            _NARROW_SIZE [_invokevirtual] = 3;
+            _NARROW_SIZE [_invokespecial] = 3;
+            _NARROW_SIZE [_invokestatic] = 3;
+
+            _NARROW_SIZE [_invokeinterface] = 5;
+                
+            _NARROW_SIZE [_new] = 3;
+            _NARROW_SIZE [_checkcast] = 3;
+            _NARROW_SIZE [_instanceof] = 3;
+
+            _NARROW_SIZE [_newarray] = 2;
+            _NARROW_SIZE [_anewarray] = 3;
+            _NARROW_SIZE [_multianewarray] = 4;
+
+            _NARROW_SIZE [_goto_w] = 5;
+            _NARROW_SIZE [_jsr_w] = 5;
+            
+            
+            _WIDE_SIZE = (int []) _NARROW_SIZE.clone ();
+            
+            _WIDE_SIZE [_iload] = 3;
+            _WIDE_SIZE [_lload] = 3;
+            _WIDE_SIZE [_fload] = 3;
+            _WIDE_SIZE [_dload] = 3;
+            _WIDE_SIZE [_aload] = 3;
+            _WIDE_SIZE [_istore] = 3;
+            _WIDE_SIZE [_lstore] = 3;
+            _WIDE_SIZE [_fstore] = 3;
+            _WIDE_SIZE [_dstore] = 3;
+            _WIDE_SIZE [_astore] = 3;
+            
+            _WIDE_SIZE [_iinc] = 5;
+            
+            _WIDE_SIZE [_ret] = 3;
+        }
+        
+    } // end of nested class
+    
+} // end of interface
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/jcd/parser/ClassDefParser.java b/core/java12/com/vladium/jcd/parser/ClassDefParser.java
new file mode 100644
index 0000000..8a9fb24
--- /dev/null
+++ b/core/java12/com/vladium/jcd/parser/ClassDefParser.java
@@ -0,0 +1,311 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassDefParser.java,v 1.1.1.1 2004/05/09 16:57:51 vlad_r Exp $
+ */
+package com.vladium.jcd.parser;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import com.vladium.jcd.cls.*;
+import com.vladium.jcd.cls.attribute.*;
+import com.vladium.jcd.cls.constant.*;
+import com.vladium.jcd.lib.UDataInputStream;
+import com.vladium.util.ByteArrayIStream;
+
+// ----------------------------------------------------------------------------
+/**
+ * This class provides an API for parsing a stream or array of bytecodes into a
+ * {@link ClassDef} AST.
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+abstract class ClassDefParser
+{
+    // public: ................................................................
+
+    
+    /**
+     * Parses an array of bytecodes into a {@link ClassDef}.
+     */
+    public static ClassDef parseClass (final byte [] bytes)
+        throws IOException
+    {
+        if (bytes == null) throw new IllegalArgumentException ("null input: bytes");
+        
+        classParser parser = new classParser (new UDataInputStream (new ByteArrayIStream (bytes)));
+        
+        return parser.class_table ();
+    }
+    
+    /**
+     * Parses an array of bytecodes into a {@link ClassDef}.
+     */
+    public static ClassDef parseClass (final byte [] bytes, final int length)
+        throws IOException
+    {
+        if (bytes == null) throw new IllegalArgumentException ("null input: bytes");
+        
+        classParser parser = new classParser (new UDataInputStream (new ByteArrayIStream (bytes, length)));
+        
+        return parser.class_table ();
+    }
+    
+    
+    /**
+     * Parses a stream of bytecodes into a {@link ClassDef}.
+     */
+    public static ClassDef parseClass (final InputStream bytes)
+        throws IOException
+    {
+        if (bytes == null) throw new IllegalArgumentException ("null input: bytes");
+        
+        classParser parser = new classParser (new UDataInputStream (bytes));
+        
+        return parser.class_table ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+
+    static final boolean PARSE_SERIAL_VERSION_UID = true;    
+    
+    static final String SERIAL_VERSION_UID_FIELD_NAME   = "serialVersionUID";
+    static final int SERIAL_VERSION_UID_FIELD_MASK      = IAccessFlags.ACC_STATIC | IAccessFlags.ACC_FINAL;
+
+    // private: ...............................................................
+
+    
+    /**
+     * All the parsing work is done by this class and its class_table method. The
+     * work that needs to be done is not complicated, but is rather monotonous -- see
+     * Chapter 4 of VM spec 1.0 for the class file format.
+     */
+    private static final class classParser
+    {
+        classParser (final UDataInputStream bytes)
+        {
+            m_bytes = bytes;
+        }
+
+        
+        ClassDef class_table () throws IOException
+        {
+            m_table = new ClassDef ();
+            
+            
+            magic ();
+            version ();
+            
+            if (DEBUG) System.out.println (s_line);
+            
+            constant_pool ();
+            
+            if (DEBUG) System.out.println (s_line);
+            
+            access_flags ();
+            this_class ();
+            super_class ();
+            
+            if (DEBUG) System.out.println (s_line);
+            
+            interfaces ();
+            if (DEBUG) System.out.println (s_line);
+            
+            fields ();
+            if (DEBUG) System.out.println (s_line);
+            
+            methods ();
+            if (DEBUG) System.out.println (s_line);
+            
+            attributes ();
+            if (DEBUG) System.out.println (s_line);
+            
+            return m_table;
+        }
+        
+        
+        void magic () throws IOException
+        {
+            final long magic = m_bytes.readU4 ();
+            if (DEBUG) System.out.println ("magic: [" + Long.toHexString (magic) + ']');
+            
+            m_table.setMagic (magic);
+        }
+        
+        
+        void version () throws IOException
+        {
+            final int minor_version = m_bytes.readU2 ();
+            final int major_version = m_bytes.readU2 ();
+            
+            if (DEBUG)
+            {
+                System.out.println ("major_version: [" + major_version + ']');
+                System.out.println ("minor_version: [" + minor_version + ']');
+            }
+            
+            m_table.setVersion (new int [] {major_version, minor_version});
+        }
+        
+        
+        void constant_pool () throws IOException
+        {
+            final int constant_pool_count = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("constant_pool_count = " + constant_pool_count + " [actual number of entries = " + (constant_pool_count - 1) + "]");
+            
+            final IConstantCollection constants = m_table.getConstants();
+            
+            for (int index = 1; index < constant_pool_count; ++ index)
+            {
+                final CONSTANT_info cp_info = CONSTANT_info.new_CONSTANT_info (m_bytes);
+                constants.add (cp_info);
+                
+                if (DEBUG) System.out.println ("[" + index + "] constant: " + cp_info);
+                
+                if ((cp_info instanceof CONSTANT_Long_info) || (cp_info instanceof CONSTANT_Double_info))
+                    index++;
+            }
+        }
+        
+        
+        void access_flags () throws IOException
+        {
+            final int _access_flags = m_bytes.readU2 ();
+            
+            m_table.setAccessFlags (_access_flags);
+        }
+        
+        
+        void this_class () throws IOException
+        {
+            final int _class_index = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("this_class: [" + _class_index + ']');
+            
+            m_table.setThisClassIndex (_class_index);
+        }
+        
+        
+        void super_class () throws IOException
+        {
+            final int _class_index = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("super_class: [" + _class_index + ']');
+            
+            m_table.setSuperClassIndex (_class_index);
+        }
+        
+        
+        void interfaces () throws IOException
+        {
+            final int _interfaces_count = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("interfaces_count = " + _interfaces_count);
+            
+            for (int i = 0; i < _interfaces_count; i++)
+            {
+                int _interface_index = m_bytes.readU2 ();
+                if (DEBUG) System.out.println ("[" + i + "] interface: " + _interface_index);
+                
+                m_table.getInterfaces().add (_interface_index);
+            }
+        }
+        
+        
+        void fields () throws IOException
+        {
+            final int _fields_count = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("fields_count = " + _fields_count);
+            
+            final IConstantCollection constantPool = m_table.getConstants ();
+            
+            for (int i = 0; i < _fields_count; i++)
+            {
+                final Field_info field_info = new Field_info (constantPool, m_bytes);
+                if (DEBUG)
+                {
+                    System.out.println ("[" + i + "] field: " + field_info);
+                    System.out.println ();
+                }
+                
+                m_table.getFields().add (field_info);
+                
+                if (PARSE_SERIAL_VERSION_UID)
+                
+                if (((field_info.getAccessFlags () & SERIAL_VERSION_UID_FIELD_MASK) == SERIAL_VERSION_UID_FIELD_MASK)
+                    && SERIAL_VERSION_UID_FIELD_NAME.equals (field_info.getName (m_table)))
+                {
+                    final IAttributeCollection attributes = field_info.getAttributes ();
+                    for (int a = 0, aLimit = attributes.size (); a < aLimit; ++ a)
+                    {
+                        final Attribute_info attr_info = attributes.get (a);
+                        
+                        if (attr_info instanceof ConstantValueAttribute_info)
+                        {
+                            final CONSTANT_literal_info constant_value = ((ConstantValueAttribute_info) attr_info).getValue (m_table);
+                            if (constant_value instanceof CONSTANT_Long_info)
+                                m_table.setDeclaredSUID (((CONSTANT_Long_info) constant_value).m_value);
+                        }
+                    }
+                }
+            }
+        }
+        
+        
+        void methods () throws IOException
+        {
+            final int _methods_count = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("methods_count = " + _methods_count);
+            
+            final IConstantCollection constantPool = m_table.getConstants ();
+            
+            for (int i = 0; i < _methods_count; i++)
+            {
+                final Method_info method_info = new Method_info (constantPool, m_bytes);
+                if (DEBUG)
+                {
+                    System.out.println ("[" + i + "] method: " + method_info);
+                    System.out.println ();
+                }
+                
+                m_table.getMethods().add (method_info);
+            }
+        }
+        
+        
+        void attributes () throws IOException
+        {
+            final int _attributes_count = m_bytes.readU2 ();
+            if (DEBUG) System.out.println ("attributes_count = " + _attributes_count);
+            
+            IConstantCollection constantPool = m_table.getConstants ();
+            
+            for (int i = 0; i < _attributes_count; i++)
+            {
+                Attribute_info attribute_info = Attribute_info.new_Attribute_info (constantPool, m_bytes);
+                if (DEBUG)
+                {
+                    System.out.println ("[" + i + "] attribute: " + attribute_info);
+                    System.out.println ();
+                }
+                
+                m_table.getAttributes().add (attribute_info);
+            }
+        }
+        
+        
+        private final UDataInputStream m_bytes;
+        private ClassDef m_table;
+    
+        private static final boolean DEBUG = false;
+        private static final String s_line = "------------------------------------------------------------------------";
+
+    } // end of static class    
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/logging/ILogLevels.java b/core/java12/com/vladium/logging/ILogLevels.java
new file mode 100644
index 0000000..33a52b5
--- /dev/null
+++ b/core/java12/com/vladium/logging/ILogLevels.java
@@ -0,0 +1,69 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ILogLevels.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.logging;
+
+// ----------------------------------------------------------------------------
+/**
+ * An enumeration of log level values used in conjunction with the API in
+ * {@link Logger} 
+ * 
+ * @see Logger
+ * 
+ * @author Vlad Roubtsov, (C) 2001
+ */
+public
+interface ILogLevels
+{
+    // public: ................................................................
+    
+    // note: must start with 0
+    
+    /** log level excluding all but severe errors */
+    int SEVERE          = 0;    // "-silent"
+    /** log level for quieter than normal operation */
+    int WARNING         = 1;    // "-quiet"
+    /** default log level */
+    int INFO            = 2;    // default
+    /** log level for chattier than normal operation */
+    int VERBOSE         = 3;    // "-verbose"
+    
+    // debug levels:
+    
+    /** debug trace log level */
+    int TRACE1          = 4;
+    /** finer debug trace log level */
+    int TRACE2          = 5;
+    /** finest debug trace log level */
+    int TRACE3          = 6;
+    
+    // special constants:
+    
+    /** setting log level to NONE disables all logging */
+    int NONE            = -1;
+    /** setting log level to ALL enables all log levels */
+    int ALL             = TRACE3 + 1;
+    
+
+    // human readable strings: 
+    
+    String SEVERE_STRING    = "severe";
+    String SILENT_STRING    = "silent";
+    String WARNING_STRING   = "warning";
+    String QUIET_STRING     = "quiet";
+    String INFO_STRING      = "info";
+    String VERBOSE_STRING   = "verbose";
+    String TRACE1_STRING    = "trace1";
+    String TRACE2_STRING    = "trace2";
+    String TRACE3_STRING    = "trace3";
+    
+    String NONE_STRING      = "none";
+    String ALL_STRING       = "all";
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/logging/Logger.java b/core/java12/com/vladium/logging/Logger.java
new file mode 100644
index 0000000..0e69a08
--- /dev/null
+++ b/core/java12/com/vladium/logging/Logger.java
@@ -0,0 +1,611 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Logger.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
+ */
+package com.vladium.logging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import com.vladium.emma.AppLoggers;
+import com.vladium.emma.IAppConstants;
+import com.vladium.util.ClassLoaderResolver;
+import com.vladium.util.Property;
+import com.vladium.util.Strings;
+
+// ----------------------------------------------------------------------------
+/**
+ * A simple Java version-independent logging framework. Each Logger is also
+ * an immutable context that encapsulates configuration elements like the
+ * logging verbosity level etc. In general, a Logger is looked up as an
+ * inheritable thread-local piece of data. This decouples classes and
+ * logging configurations in a way that seems difficult with log4j.<P>
+ * 
+ * Note that a given class is free to cache its context in an instance field
+ * if the class is instantiated and used only on a single thread (or a set of
+ * threads that are guaranteed to share the same logging context). [This is
+ * different from the usual log4j pattern of caching a logger in a class static
+ * field]. In other cases (e.g., the instrumentation runtime), it makes more
+ * sense to scope a context to a single method invocation.<P>
+ * 
+ * Every log message is structured as follows:
+ * <OL>
+ *  <LI> message is prefixed with the prefix string set in the Logger if that is
+ * not null;
+ *  <LI> if the calling class could be identified and it supplied the calling
+ * method name, the calling method is identified with all name components that
+ * are not null;
+ *  <LI> caller-supplied message is logged, if not null;
+ *  <LI> caller-supplied Throwable is dumped starting with a new line, if not null.
+ * </OL>
+ * 
+ * MT-safety: a given Logger instance will not get corrupted by concurrent
+ * usage from multiple threads and guarantees that data written to the underlying
+ * PrintWriter in a single log call will be done in one atomic print() step.
+ * 
+ * @see ILogLevels
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class Logger implements ILogLevels
+{
+    // public: ................................................................
+    
+    // TODO: update javadoc for 'logCaller'
+    // TODO: need isLoggable (Class)
+    
+    public static Logger create (final int level, final PrintWriter out, final String prefix, final Set classMask)
+    {
+        if ((level < NONE) || (level > ALL))
+            throw new IllegalArgumentException ("invalid log level: " + level);
+        
+        if ((out == null) || out.checkError ())
+            throw new IllegalArgumentException ("null or corrupt input: out");
+        
+        return new Logger (level, out, prefix, classMask);
+    }
+    
+    /**
+     * This works as a cloning creator of sorts.
+     * 
+     * @param level
+     * @param out
+     * @param prefix
+     * @param classMask
+     * @param base
+     * @return
+     */
+    public static Logger create (final int level, final PrintWriter out, final String prefix, final Set classMask,
+                                 final Logger base)
+    {
+        if (base == null)
+        {
+            return create (level, out, prefix, classMask);
+        }
+        else
+        {
+            final int _level = level >= NONE
+                ? level
+                : base.m_level;
+                
+            final PrintWriter _out = (out != null) && ! out.checkError ()
+                ? out
+                : base.m_out;
+            
+            // TODO: do a better job of logger cloning
+            final String _prefix = prefix;
+//            final String _prefix = prefix != null
+//                ? prefix
+//                : base.m_prefix;
+            
+            final Set _classMask = classMask != null
+                ? classMask
+                : base.m_classMask;
+        
+        
+            return new Logger (_level, _out, _prefix, _classMask);
+        }
+    }
+  
+
+    /**
+     * A quick method to determine if logging is enabled at a given level.
+     * This method acquires no monitors and should be used when calling one of
+     * log() or convenience logging methods directly incurs significant
+     * parameter construction overhead.
+     * 
+     * @see ILogLevels
+     */
+    public final boolean isLoggable (final int level)
+    {
+        return (level <= m_level);
+    }
+
+    /**
+     * A convenience method equivalent to isLoggable(INFO).
+     */
+    public final boolean atINFO ()
+    {
+        return (INFO <= m_level);
+    }
+    
+    /**
+     * A convenience method equivalent to isLoggable(VERBOSE).
+     */
+    public final boolean atVERBOSE ()
+    {
+        return (VERBOSE <= m_level);
+    }
+    
+    /**
+     * A convenience method equivalent to isLoggable(TRACE1).
+     */
+    public final boolean atTRACE1 ()
+    {
+        return (TRACE1 <= m_level);
+    }
+
+    /**
+     * A convenience method equivalent to isLoggable(TRACE2).
+     */
+    public final boolean atTRACE2 ()
+    {
+        return (TRACE2 <= m_level);
+    }
+
+    /**
+     * A convenience method equivalent to isLoggable(TRACE3).
+     */
+    public final boolean atTRACE3 ()
+    {
+        return (TRACE3 <= m_level);
+    }
+
+
+    /**
+     * A convenience method to log 'msg' from an anonymous calling method
+     * at WARNING level.
+     * 
+     * @param msg log message [ignored if null]
+     */
+    public final void warning (final String msg)
+    {
+        _log (WARNING, null, msg, false);
+    }
+    
+    /**
+     * A convenience method to log 'msg' from an anonymous calling method
+     * at INFO level.
+     * 
+     * @param msg log message [ignored if null]
+     */
+    public final void info (final String msg)
+    {
+        _log (INFO, null, msg, false);
+    }
+    
+    /**
+     * A convenience method to log 'msg' from an anonymous calling method
+     * at VERBOSE level.
+     * 
+     * @param msg log message [ignored if null]
+     */
+    public final void verbose (final String msg)
+    {
+        _log (VERBOSE, null, msg, false);
+    }
+
+    
+    /**
+     * A convenience method equivalent to log(TRACE1, method, msg).
+     * 
+     * @param method calling method name [ignored if null]
+     * @param msg log message [ignored if null]
+     */
+    public final void trace1 (final String method, final String msg)
+    {
+        _log (TRACE1, method, msg, true);
+    }
+    
+    /**
+     * A convenience method equivalent to log(TRACE2, method, msg).
+     * 
+     * @param method calling method name [ignored if null]
+     * @param msg log message [ignored if null]
+     */
+    public final void trace2 (final String method, final String msg)
+    {
+        _log (TRACE2, method, msg, true);
+    }
+    
+    /**
+     * A convenience method equivalent to log(TRACE3, method, msg).
+     * 
+     * @param method calling method name [ignored if null]
+     * @param msg log message [ignored if null]
+     */
+    public final void trace3 (final String method, final String msg)
+    {
+        _log (TRACE3, method, msg, true);
+    }
+
+    /**
+     * Logs 'msg' from an unnamed calling method.
+     * 
+     * @param level level to log at [the method does nothing if this is less
+     * than the set level].
+     * @param msg log message [ignored if null]
+     */        
+    public final void log (final int level, final String msg, final boolean logCaller)
+    {
+        _log (level, null, msg, logCaller);
+    }
+    
+    /**
+     * Logs 'msg' from a given calling method.
+     * 
+     * @param level level to log at [the method does nothing if this is less
+     * than the set level].
+     * @param method calling method name [ignored if null]
+     * @param msg log message [ignored if null]
+     */        
+    public final void log (final int level, final String method, final String msg, final boolean logCaller)
+    {
+        _log (level, method, msg, logCaller);
+    }
+
+    /**
+     * Logs 'msg' from an unnamed calling method followed by the 'throwable' stack
+     * trace dump.
+     *  
+     * @param level level to log at [the method does nothing if this is less
+     * than the set level].
+     * @param msg log message [ignored if null]
+     * @param throwable to dump after message [ignored if null]
+     */
+    public final void log (final int level, final String msg, final Throwable throwable)
+    {
+        _log (level, null, msg, throwable);
+    }
+    
+    /**
+     * Logs 'msg' from a given calling method followed by the 'throwable' stack
+     * trace dump.
+     *  
+     * @param level level to log at [the method does nothing if this is less
+     * than the set level].
+     * @param method calling method name [ignored if null]
+     * @param msg log message [ignored if null]
+     * @param throwable to dump after message [ignored if null]
+     */
+    public final void log (final int level, final String method, final String msg, final Throwable throwable)
+    {
+        _log (level, method, msg, throwable);
+    }
+
+    
+    /**
+     * Provides direct access to the PrintWriter used by this Logger. 
+     * 
+     * @return print writer used by this logger [never null]
+     */
+    public PrintWriter getWriter ()
+    {
+        return m_out;
+    }
+
+    
+    /**
+     * Returns the current top of the thread-local logger stack or the static
+     * Logger instance scoped to Logger.class if the stack is empty.
+     * 
+     * @return current logger [never null]
+     */
+    public static Logger getLogger ()
+    {
+        final LinkedList stack = (LinkedList) THREAD_LOCAL_STACK.get ();
+        
+        // [assertion: stack != null]
+
+        if (stack.isEmpty ())
+        {
+            return STATIC_LOGGER;
+        }
+        else
+        {
+            return (Logger) stack.getLast ();
+        }
+    }
+    
+    /**
+     * 
+     * @param ctx [may not be null]
+     */
+    public static void push (final Logger ctx)
+    {
+        if (ctx == null)
+            throw new IllegalArgumentException ("null input: ctx");
+        
+        final LinkedList stack = (LinkedList) THREAD_LOCAL_STACK.get ();
+        stack.addLast (ctx);
+    }
+    
+    /**
+     * Requiring a context parameter here helps enforce correct push/pop
+     * nesting in the caller code.
+     * 
+     * @param ctx [may not be null]
+     */
+    public static void pop (final Logger ctx)
+    {
+        // TODO: add guards for making sure only the pushing thread is allowed to
+        // execute this
+        
+        final LinkedList stack = (LinkedList) THREAD_LOCAL_STACK.get ();
+
+        try
+        {
+            final Logger current = (Logger) stack.getLast ();
+            if (current != ctx)
+                throw new IllegalStateException ("invalid context being popped: " + ctx);
+            
+            stack.removeLast ();
+            current.cleanup ();
+        }
+        catch (NoSuchElementException nsee)
+        {
+            throw new IllegalStateException ("empty logger context stack on thread [" + Thread.currentThread () + "]: " + nsee);
+        }
+    }
+
+    
+    public static int stringToLevel (final String level)
+    {
+        if (ILogLevels.SEVERE_STRING.equalsIgnoreCase (level) || ILogLevels.SILENT_STRING.equalsIgnoreCase (level))
+            return ILogLevels.SEVERE;
+        else if (ILogLevels.WARNING_STRING.equalsIgnoreCase (level) || ILogLevels.QUIET_STRING.equalsIgnoreCase (level))
+            return ILogLevels.WARNING;
+        else if (ILogLevels.INFO_STRING.equalsIgnoreCase (level))
+            return ILogLevels.INFO;
+        else if (ILogLevels.VERBOSE_STRING.equalsIgnoreCase (level))
+            return ILogLevels.VERBOSE;
+        else if (ILogLevels.TRACE1_STRING.equalsIgnoreCase (level))
+            return ILogLevels.TRACE1;
+        else if (ILogLevels.TRACE2_STRING.equalsIgnoreCase (level))
+            return ILogLevels.TRACE2;
+        else if (ILogLevels.TRACE3_STRING.equalsIgnoreCase (level))
+            return ILogLevels.TRACE3;
+        else if (ILogLevels.NONE_STRING.equalsIgnoreCase (level))
+            return ILogLevels.NONE;
+        else if (ILogLevels.ALL_STRING.equalsIgnoreCase (level))
+            return ILogLevels.ALL;
+        else
+        {
+            int _level = Integer.MIN_VALUE;
+            try
+            {
+                _level = Integer.parseInt (level);
+            }
+            catch (Exception ignore) {}
+            
+            if ((_level >= ILogLevels.NONE) && (_level <= ILogLevels.ALL))
+                return _level;
+            else
+                return ILogLevels.INFO; // default to something middle of the ground
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class ThreadLocalStack extends InheritableThreadLocal
+    {
+        protected Object initialValue ()
+        {
+            return new LinkedList ();
+        }
+        
+    } // end of nested class
+
+    
+    private Logger (final int level, final PrintWriter out, final String prefix, final Set classMask)
+    {
+        m_level = level;
+        m_out = out;
+        m_prefix = prefix;
+        m_classMask = classMask; // no defensive clone
+    }
+
+    private void cleanup ()
+    {
+        m_out.flush ();
+    }
+    
+    private void _log (final int level, final String method,
+                       final String msg, final boolean logCaller)
+    {
+        if ((level <= m_level) && (level >= SEVERE))
+        {
+            final Class caller = logCaller ? ClassLoaderResolver.getCallerClass (2) : null;
+            final StringBuffer buf = new StringBuffer (m_prefix != null ? m_prefix + ": " : "");
+
+            if ((caller != null) || (method != null))
+            {
+                buf.append ("[");
+                
+                if (caller != null) // if the caller could not be determined, s_classMask is ignored
+                {
+                    String callerName = caller.getName ();
+                    
+                    if (callerName.startsWith (PREFIX_TO_STRIP))
+                        callerName = callerName.substring (PREFIX_TO_STRIP_LENGTH);
+                        
+                    String parentName = callerName;
+                    
+                    final int firstDollar = callerName.indexOf ('$');
+                    if (firstDollar > 0) parentName = callerName.substring (0, firstDollar);
+                    
+                    if ((m_classMask == null) || m_classMask.contains (parentName))
+                        buf.append (callerName);
+                    else
+                        return;
+                }
+                
+                if (method != null)
+                {
+                    buf.append ("::");
+                    buf.append (method);
+                }
+                
+                buf.append ("] ");
+            }
+
+            final PrintWriter out = m_out;
+
+            if (msg != null) buf.append (msg);
+            
+            out.println (buf);
+            if (FLUSH_LOG) out.flush ();
+        }
+    }
+    
+    private void _log (final int level, final String method,
+                       final String msg, final Throwable throwable)
+    {        
+        if ((level <= m_level) && (level >= SEVERE))
+        {
+            final Class caller = ClassLoaderResolver.getCallerClass (2);
+            final StringBuffer buf = new StringBuffer (m_prefix != null ? m_prefix + ": " : "");
+            
+            if ((caller != null) || (method != null))
+            {
+                buf.append ("[");
+                
+                if (caller != null) // if the caller could not be determined, s_classMask is ignored
+                {
+                    String callerName = caller.getName ();
+                    
+                    if (callerName.startsWith (PREFIX_TO_STRIP))
+                        callerName = callerName.substring (PREFIX_TO_STRIP_LENGTH);
+                        
+                    String parentName = callerName;
+                    
+                    final int firstDollar = callerName.indexOf ('$');
+                    if (firstDollar > 0) parentName = callerName.substring (0, firstDollar);
+                    
+                    if ((m_classMask == null) || m_classMask.contains (parentName))
+                        buf.append (callerName);
+                    else
+                        return;
+                }
+                
+                if (method != null)
+                {
+                    buf.append ("::");
+                    buf.append (method);
+                }
+                
+                buf.append ("] ");
+            }
+            
+            final PrintWriter out = m_out;
+            
+            if (msg != null) buf.append (msg);
+            
+            if (throwable != null)
+            {
+                final StringWriter sw = new StringWriter ();
+                final PrintWriter pw = new PrintWriter (sw);
+                
+                throwable.printStackTrace (pw);
+                pw.flush ();
+                
+                buf.append (sw.toString ());
+            }
+
+            out.println (buf);
+            if (FLUSH_LOG) out.flush ();
+        }
+    }
+
+    
+
+    private final int m_level; // always in [NONE, ALL] range
+    private final PrintWriter m_out; // never null
+    private final String m_prefix; // null is equivalent to no prefix
+    private final Set /* String */ m_classMask; // null is equivalent to no class filtering
+
+    private static final String PREFIX_TO_STRIP = "com.vladium."; // TODO: can this be set programmatically ?
+    private static final int PREFIX_TO_STRIP_LENGTH = PREFIX_TO_STRIP.length ();
+    private static final boolean FLUSH_LOG = true;
+    private static final String COMMA_DELIMITERS    = "," + Strings.WHITE_SPACE;
+
+    private static final Logger STATIC_LOGGER; // set in <clinit>
+    private static final ThreadLocalStack THREAD_LOCAL_STACK; // set in <clinit>
+    
+    static
+    {
+        THREAD_LOCAL_STACK = new ThreadLocalStack ();
+        
+        // TODO: unfortunately, this init code makes Logger coupled to the app classes
+        // (via the app namespace string constants)
+        // I don't quite see an elegant solution to this design problem yet
+        
+        final Properties properties = Property.getAppProperties (IAppConstants.APP_NAME_LC, Logger.class.getClassLoader ());
+        
+        // verbosity level:
+        
+        final int level;
+        {
+            final String _level = properties.getProperty (AppLoggers.PROPERTY_VERBOSITY_LEVEL,
+                                                          AppLoggers.DEFAULT_VERBOSITY_LEVEL);
+            level = stringToLevel (_level);
+        }
+        
+        // verbosity filter:
+        
+        final Set filter;
+        {
+            final String _filter = properties.getProperty (AppLoggers.PROPERTY_VERBOSITY_FILTER);
+            Set temp = null;
+            
+            if (_filter != null)
+            {
+                final StringTokenizer tokenizer = new StringTokenizer (_filter, COMMA_DELIMITERS);
+                if (tokenizer.countTokens () > 0)
+                {
+                    temp = new HashSet (tokenizer.countTokens ());
+                    while (tokenizer.hasMoreTokens ())
+                    {
+                        temp.add (tokenizer.nextToken ());
+                    }
+                }
+            }
+            
+            filter = temp;
+        }
+        
+        
+        STATIC_LOGGER = create (level,
+                                new PrintWriter (System.out, false),
+                                IAppConstants.APP_NAME,
+                                filter);
+    }
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/ByteArrayIStream.java b/core/java12/com/vladium/util/ByteArrayIStream.java
new file mode 100644
index 0000000..81b8d81
--- /dev/null
+++ b/core/java12/com/vladium/util/ByteArrayIStream.java
@@ -0,0 +1,125 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ByteArrayIStream.java,v 1.1.1.1 2004/05/09 16:57:51 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.InputStream;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * An unsynchronized version of java.io.ByteArrayInputStream.<p>
+ * 
+ * All argument validation is disabled in release mode.<p>
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class ByteArrayIStream extends InputStream
+{
+    // public: ................................................................
+
+
+    public ByteArrayIStream (final byte [] buf)
+    {
+        this (buf, buf.length);
+    }
+
+    public ByteArrayIStream (final byte [] buf, final int length)
+    {
+        if ($assert.ENABLED) $assert.ASSERT ((length >= 0) && (length <= buf.length),
+            "invalid length: " + length);
+        
+        m_buf = buf;
+        m_max = length;
+    }    
+
+    // InputStream:
+    
+    public final int read ()
+    {
+        if (m_pos >= m_max)
+            return -1;
+        else
+            return 0xFF & m_buf [m_pos ++];
+    }
+
+    public final int read (final byte [] buf, final int offset, int length)
+    {
+        if ($assert.ENABLED)
+            $assert.ASSERT ((offset >= 0) && (offset <= buf.length) &&
+                (length >= 0) && ((offset + length) <= buf.length),
+                "invalid input (" + buf.length + ", " + offset + ", " + length + ")");
+        
+        final int pos = m_pos;
+        final int max = m_max;
+        
+        if (pos >= max) return -1;
+        if (pos + length > max) length = max - pos;
+        if (length <= 0) return 0;
+        
+        final byte [] mbuf = m_buf;
+        
+        if (length < NATIVE_COPY_THRESHOLD)
+            for (int i = 0; i < length; ++ i) buf [offset + i] = mbuf [pos + i];
+        else
+            System.arraycopy (mbuf, pos, buf, offset, length);
+            
+        m_pos += length;
+        
+        return length;
+    }
+    
+    public final int available ()
+    {
+        return m_max - m_pos;
+    }
+    
+    public final long skip (long n)
+    {
+        if (m_pos + n > m_max) n = m_max - m_pos;
+
+        if (n < 0) return 0;        
+        m_pos += n;
+        
+        return n;
+    }
+    
+    /**
+     * Differs from the contruct for InputStream.reset() in that this method
+     * always resets the stream to the same it was immediately after creation.
+     */
+    public final void reset ()
+    {
+        m_pos = 0;
+    }
+
+    /**
+     * Equivalent to {@link #reset()}. 
+     */
+    public final void close ()
+    {
+        reset ();
+    }
+
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private final byte [] m_buf;
+    private final int m_max;
+    private int m_pos;
+
+    private static final int NATIVE_COPY_THRESHOLD  = 9;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/ByteArrayOStream.java b/core/java12/com/vladium/util/ByteArrayOStream.java
new file mode 100644
index 0000000..59526f9
--- /dev/null
+++ b/core/java12/com/vladium/util/ByteArrayOStream.java
@@ -0,0 +1,320 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ByteArrayOStream.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * An unsynchronized version of java.io.ByteArrayOutputStream that can expose
+ * the underlying byte array without a defensive clone and can also be converted
+ * to a {@link ByteArrayIStream} without intermediate array copies.<p> 
+ * 
+ * All argument validation is disabled in release mode.<p>
+ * 
+ * NOTE: copy-on-write not supported
+ * 
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class ByteArrayOStream extends OutputStream
+{
+    // public: ................................................................
+    
+    /**
+     * Callee takes ownership of 'buf'.
+     */
+    public ByteArrayOStream (final int initialCapacity)
+    {
+        if ($assert.ENABLED)
+            $assert.ASSERT (initialCapacity >= 0, "negative initial capacity: " + initialCapacity); 
+        
+        m_buf = new byte [initialCapacity];
+    }
+    
+    public final ByteArrayIStream toByteIStream ()
+    {
+        return new ByteArrayIStream (m_buf, m_pos);
+    }
+    
+    public final void write2 (final int b1, final int b2)
+    {
+        final int pos = m_pos;
+        final int capacity = pos + 2;
+        byte [] mbuf = m_buf;
+        final int mbuflen = mbuf.length;
+        
+        if (mbuflen < capacity)
+        {
+            final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
+        
+            if (pos < NATIVE_COPY_THRESHOLD)
+                for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
+            else
+                System.arraycopy (mbuf, 0, newbuf, 0, pos);
+            
+            m_buf = mbuf = newbuf;
+        }
+        
+        mbuf [pos] = (byte) b1;
+        mbuf [pos + 1] = (byte) b2;
+        m_pos = capacity;
+    }
+    
+    public final void write3 (final int b1, final int b2, final int b3)
+    {
+        final int pos = m_pos;
+        final int capacity = pos + 3;
+        byte [] mbuf = m_buf;
+        final int mbuflen = mbuf.length;
+        
+        if (mbuflen < capacity)
+        {
+            final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
+        
+            if (pos < NATIVE_COPY_THRESHOLD)
+                for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
+            else
+                System.arraycopy (mbuf, 0, newbuf, 0, pos);
+            
+            m_buf = mbuf = newbuf;
+        }
+        
+        mbuf [pos] = (byte) b1;
+        mbuf [pos + 1] = (byte) b2;
+        mbuf [pos + 2] = (byte) b3;
+        m_pos = capacity;
+    }
+    
+    public final void write4 (final int b1, final int b2, final int b3, final int b4)
+    {
+        final int pos = m_pos;
+        final int capacity = pos + 4;
+        byte [] mbuf = m_buf;
+        final int mbuflen = mbuf.length;
+        
+        if (mbuflen < capacity)
+        {
+            final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
+        
+            if (pos < NATIVE_COPY_THRESHOLD)
+                for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
+            else
+                System.arraycopy (mbuf, 0, newbuf, 0, pos);
+            
+            m_buf = mbuf = newbuf;
+        }
+        
+        mbuf [pos] = (byte) b1;
+        mbuf [pos + 1] = (byte) b2;
+        mbuf [pos + 2] = (byte) b3;
+        mbuf [pos + 3] = (byte) b4;
+        m_pos = capacity;
+    }
+    
+    public final void writeTo (final OutputStream out)
+        throws IOException
+    {
+        out.write (m_buf, 0, m_pos);
+    }
+    
+//    public final void readFully (final InputStream in)
+//        throws IOException
+//    {
+//        while (true)
+//        {
+//            int chunk = in.available ();
+//            
+//            System.out.println ("available = " + chunk);
+//            
+//            // TODO: this case is handled poorly (on EOF)
+//            if (chunk == 0) chunk = READ_CHUNK_SIZE;
+//            
+//            // read at least 'available' bytes: extend the capacity as needed
+//        
+//            int free = m_buf.length - m_pos;
+//            
+//            final int read;
+//            if (free > chunk)
+//            {
+//                // try reading more than 'chunk' anyway:
+//                read = in.read (m_buf, m_pos, free);
+//            }
+//            else
+//            {
+//                // extend the capacity to match 'chunk':
+//                {
+//                    System.out.println ("reallocation");
+//                    final byte [] newbuf = new byte [m_pos + chunk];
+//            
+//                    if (m_pos < NATIVE_COPY_THRESHOLD)
+//                        for (int i = 0; i < m_pos; ++ i) newbuf [i] = m_buf [i];
+//                    else
+//                        System.arraycopy (m_buf, 0, newbuf, 0, m_pos);
+//                
+//                    m_buf = newbuf;
+//                }
+//                
+//                read = in.read (m_buf, m_pos, chunk);
+//            }
+//
+//            if (read < 0)
+//                break;
+//            else
+//                m_pos += read;
+//        }
+//    }
+    
+//    public final void addCapacity (final int extraCapacity)
+//    {
+//        final int pos = m_pos;
+//        final int capacity = pos + extraCapacity;
+//        byte [] mbuf = m_buf;
+//        final int mbuflen = mbuf.length;
+//        
+//        if (mbuflen < capacity)
+//        {
+//            final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
+//        
+//            if (pos < NATIVE_COPY_THRESHOLD)
+//                for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
+//            else
+//                System.arraycopy (mbuf, 0, newbuf, 0, pos);
+//            
+//            m_buf = newbuf;
+//        }
+//    }
+    
+    public final byte [] getByteArray ()
+    {
+        return m_buf;
+    }
+
+    /**
+     * 
+     * @return [result.length = size()]
+     */    
+    public final byte [] copyByteArray ()
+    {
+        final int pos = m_pos;
+        
+        final byte [] result = new byte [pos];
+        final byte [] mbuf = m_buf;
+        
+        if (pos < NATIVE_COPY_THRESHOLD)
+            for (int i = 0; i < pos; ++ i) result [i] = mbuf [i];
+        else
+            System.arraycopy (mbuf, 0, result, 0, pos);
+        
+        return result;
+    }
+    
+    public final int size ()
+    {
+        return m_pos;
+    }
+    
+    public final int capacity ()
+    {
+        return m_buf.length;
+    }
+    
+    /**
+     * Does not reduce the current capacity.
+     */
+    public final void reset ()
+    {
+        m_pos = 0;
+    }
+    
+    // OutputStream:
+    
+    public final void write (final int b)
+    {
+        final int pos = m_pos;
+        final int capacity = pos + 1;
+        byte [] mbuf = m_buf;
+        final int mbuflen = mbuf.length;
+        
+        if (mbuflen < capacity)
+        {
+            final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
+            
+            if (pos < NATIVE_COPY_THRESHOLD)
+                for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
+            else
+                System.arraycopy (mbuf, 0, newbuf, 0, pos);
+            
+            m_buf = mbuf = newbuf;
+        }
+        
+        mbuf [pos] = (byte) b;
+        m_pos = capacity;
+    }
+
+
+    public final void write (final byte [] buf, final int offset, final int length)
+    {
+        if ($assert.ENABLED)
+            $assert.ASSERT ((offset >= 0) && (offset <= buf.length) &&
+                (length >= 0) && ((offset + length) <= buf.length),
+                "invalid input (" + buf.length + ", " + offset + ", " + length + ")");
+        
+        final int pos = m_pos;
+        final int capacity = pos + length;
+        byte [] mbuf = m_buf;
+        final int mbuflen = mbuf.length;
+        
+        if (mbuflen < capacity)
+        {
+            final byte [] newbuf = new byte [Math.max (mbuflen << 1, capacity)];
+            
+            if (pos < NATIVE_COPY_THRESHOLD)
+                for (int i = 0; i < pos; ++ i) newbuf [i] = mbuf [i];
+            else
+                System.arraycopy (mbuf, 0, newbuf, 0, pos);
+            
+            m_buf = mbuf = newbuf;
+        }
+        
+        if (length < NATIVE_COPY_THRESHOLD)
+            for (int i = 0; i < length; ++ i) mbuf [pos + i] = buf [offset + i];
+        else
+            System.arraycopy (buf, offset, mbuf, pos, length);
+            
+        m_pos = capacity; 
+    }
+
+    
+    /**
+     * Equivalent to {@link #reset()}. 
+     */
+    public final void close ()
+    {
+        reset ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private byte [] m_buf;
+    private int m_pos;
+    
+//    private static final int READ_CHUNK_SIZE        = 16 * 1024;
+    private static final int NATIVE_COPY_THRESHOLD  = 9;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/ClassLoadContext.java b/core/java12/com/vladium/util/ClassLoadContext.java
new file mode 100644
index 0000000..39802dc
--- /dev/null
+++ b/core/java12/com/vladium/util/ClassLoadContext.java
@@ -0,0 +1,56 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassLoadContext.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ * Information context for {@link IClassLoadStrategy#getClassLoader(ClassLoadContext)}.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class ClassLoadContext
+{
+    // public: ................................................................
+    
+    
+    /**
+     * Returns the class representing the caller of {@link ClassLoaderResolver}
+     * API. Can be used to retrieve the caller's classloader etc (which may be
+     * different from the ClassLoaderResolver's own classloader) ['null' if caller
+     * resolver could be instantiated due to security restrictions]. 
+     */
+    public final Class getCallerClass ()
+    {
+        return m_caller;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    /**
+     * This constructor is package-private to restrict instantiation to
+     * {@link ClassLoaderResolver} only.
+     * 
+     * @param caller [can be null]
+     */
+    ClassLoadContext (final Class caller)
+    {
+        m_caller = caller;
+    }
+    
+    // private: ...............................................................
+    
+
+    private final Class m_caller;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/ClassLoaderResolver.java b/core/java12/com/vladium/util/ClassLoaderResolver.java
new file mode 100644
index 0000000..caa35a8
--- /dev/null
+++ b/core/java12/com/vladium/util/ClassLoaderResolver.java
@@ -0,0 +1,228 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassLoaderResolver.java,v 1.1.1.1.2.2 2004/07/10 03:34:53 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ * This non-instantiable non-subclassable class acts as the global point for
+ * choosing a ClassLoader for dynamic class/resource loading at any point
+ * in an application.
+ * 
+ * @see ResourceLoader
+ * @see IClassLoadStrategy
+ * @see DefaultClassLoadStrategy
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class ClassLoaderResolver
+{
+    // public: ................................................................
+    
+    // NOTE: don't use Logger in this class to avoid infinite recursion
+
+    /**
+     * This method selects the "best" classloader instance to be used for
+     * class/resource loading by whoever calls this method. The decision
+     * typically involves choosing between the caller's current, thread context,
+     * system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}
+     * instance established by the last call to {@link #setStrategy}.<P>
+     * 
+     * This method does not throw.
+     * 
+     * @param caller [null input eliminates the caller's current classloader
+     * from consideration]
+     * 
+     * @return classloader to be used by the caller ['null' indicates the
+     * primordial loader]
+     */
+    public static synchronized ClassLoader getClassLoader (final Class caller)
+    {
+        final ClassLoadContext ctx = new ClassLoadContext (caller);
+        
+        return s_strategy.getClassLoader (ctx); 
+    }
+
+    /**
+     * This method selects the "best" classloader instance to be used for
+     * class/resource loading by whoever calls this method. The decision
+     * typically involves choosing between the caller's current, thread context,
+     * system, and other classloaders in the JVM and is made by the {@link IClassLoadStrategy}
+     * instance established by the last call to {@link #setStrategy}.<P>
+     * 
+     * This method uses its own caller to set the call context. To be able to
+     * override this decision explicitly, use {@link #getClassLoader(Class)}.<P> 
+     * 
+     * This method does not throw.
+     * 
+     * @return classloader to be used by the caller ['null' indicates the
+     * primordial loader]
+     */
+    public static synchronized ClassLoader getClassLoader ()
+    {
+        final Class caller = getCallerClass (1); // 'caller' can be set to null
+        final ClassLoadContext ctx = new ClassLoadContext (caller);
+        
+        return s_strategy.getClassLoader (ctx); 
+    }
+
+    /*
+     * Indexes into the current method call context with a given offset. Offset 0
+     * corresponds to the immediate caller, offset 1 corresponds to its caller,
+     * etc.<P>
+     * 
+     * Invariant: getCallerClass(0) == class containing code that performs this call 
+     */
+    public static Class getCallerClass (final int callerOffset)
+    {
+        if (CALLER_RESOLVER == null) return null; // only happens if <clinit> failed
+
+        return CALLER_RESOLVER.getClassContext () [CALL_CONTEXT_OFFSET + callerOffset];
+    }
+
+    /**
+     * Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
+     * 'loader1'=='loader2']. Of course, this works only for classloaders that
+     * set their parent pointers correctly. 'null' is interpreted as the
+     * primordial loader [i.e., everybody's parent].
+     */ 
+    public static boolean isChild (final ClassLoader loader1, ClassLoader loader2)
+    {
+        if (loader1 == loader2) return true; 
+        if (loader2 == null) return false; 
+        if (loader1 == null) return true;
+        
+        for ( ; loader2 != null; loader2 = loader2.getParent ())
+        {
+            if (loader2 == loader1) return true;
+        }   
+
+        return false;
+    }
+
+    /**
+     * Gets the current classloader selection strategy setting. 
+     */
+    public static synchronized IClassLoadStrategy getStrategy ()
+    {
+        return s_strategy;
+    }
+
+    /**
+     * Sets the classloader selection strategy to be used by subsequent calls
+     * to {@link #getClassLoader()}. An instance of {@link ClassLoaderResolver.DefaultClassLoadStrategy}
+     * is in effect if this method is never called.
+     *  
+     * @param strategy new strategy [may not be null] 
+     * @return previous setting
+     */    
+    public static synchronized IClassLoadStrategy setStrategy (final IClassLoadStrategy strategy)
+    {
+        if (strategy == null) throw new IllegalArgumentException ("null input: strategy");
+        
+        final IClassLoadStrategy old = s_strategy;
+        s_strategy = strategy;
+        
+        return old;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class DefaultClassLoadStrategy implements IClassLoadStrategy
+    {
+        public ClassLoader getClassLoader (final ClassLoadContext ctx)
+        {
+            if (ctx == null) throw new IllegalArgumentException ("null input: ctx");
+            
+            final Class caller = ctx.getCallerClass ();
+            final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
+            
+            ClassLoader result;
+            
+            if (caller == null)
+                result = contextLoader;
+            else
+            {
+                final ClassLoader callerLoader = caller.getClassLoader ();
+                
+                // if 'callerLoader' and 'contextLoader' are in a parent-child
+                // relationship, always choose the child:
+                
+                // SF BUG 975080: change the sibling case to use 'callerLoader'
+                // to work around ANT 1.6.x incorrect classloading model:
+                
+                if (isChild (callerLoader, contextLoader))
+                    result = contextLoader;
+                else
+                    result = callerLoader;
+            }
+            
+            final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();
+            
+            // precaution for when deployed as a bootstrap or extension class:
+            if (isChild (result, systemLoader))
+                result = systemLoader;
+            
+            return result;
+        }
+    
+    } // end of nested class
+    
+    
+    /**
+     * A helper class to get the call context. It subclasses SecurityManager
+     * to make getClassContext() accessible. An instance of CallerResolver
+     * only needs to be created, not installed as an actual security
+     * manager.
+     */
+    private static final class CallerResolver extends SecurityManager
+    {
+        protected Class [] getClassContext ()
+        {
+            return super.getClassContext ();
+        }
+        
+    } // end of nested class 
+    
+    
+    private ClassLoaderResolver () {} // prevent subclassing
+
+    
+    private static IClassLoadStrategy s_strategy; // initialized in <clinit>
+    
+    private static final int CALL_CONTEXT_OFFSET = 2; // may need to change if this class is redesigned
+    private static final CallerResolver CALLER_RESOLVER; // set in <clinit>
+    //private static Throwable CLINIT_FAILURE;
+    
+    static
+    {
+        CallerResolver temp = null;
+        try
+        {
+            // this can fail if the current SecurityManager does not allow
+            // RuntimePermission ("createSecurityManager"):
+            
+            temp = new CallerResolver ();
+        }
+        catch (Throwable t)
+        {
+            //CLINIT_FAILURE = t;
+        }
+        CALLER_RESOLVER = temp;
+        
+        s_strategy = new DefaultClassLoadStrategy ();
+    }
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/Descriptors.java b/core/java12/com/vladium/util/Descriptors.java
new file mode 100644
index 0000000..de8b4aa
--- /dev/null
+++ b/core/java12/com/vladium/util/Descriptors.java
@@ -0,0 +1,239 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Descriptors.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import com.vladium.jcd.cls.IClassDefConstants;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Descriptors
+{
+    // public: ................................................................
+
+    // TODO: some overlap with Types in c.v.jcp.lib
+    
+    public static final char JAVA_NAME_SEPARATOR = '.';
+    public static final char VM_NAME_SEPARATOR = '/';
+    
+    public static String combine (final String packageName, final String name, final char separator)
+    {
+        if ((name == null) || (name.length () == 0))
+            throw new IllegalArgumentException ("null or empty input: name");
+            
+        if ((packageName == null) || (packageName.length () == 0))
+            return name;
+        else
+            return new StringBuffer (packageName).append (separator).append (name).toString ();
+    }
+    
+    public static String combineJavaName (final String packageName, final String name)
+    {
+        return combine (packageName, name, JAVA_NAME_SEPARATOR);
+    }
+    
+    public static String combineVMName (final String packageName, final String name)
+    {
+        return combine (packageName, name, VM_NAME_SEPARATOR);
+    }
+    
+    /**
+     * Converts a Java package/class name to how it would be
+     * represented in the VM.<P>
+     * 
+     * Example:
+     * <PRE><CODE>
+     * javaNameToVMName("java.lang.Object") = "java/lang/Object"
+     * </CODE></PRE>
+     * 
+     * @see #vmNameToJavaName
+     */
+    public static String javaNameToVMName (final String javaName)
+    {
+        if (javaName == null) return null;
+        
+        return javaName.replace ('.', '/');
+    }
+    
+    /**
+     * Converts a JVM package/class name to how it would be
+     * represented in Java.<P>
+     * 
+     * Example:
+     * <PRE><CODE>
+     * vmNameToJavaName("java/lang/Object") = "java.lang.Object"
+     * </CODE></PRE>
+     * 
+     * @see #javaNameToVMName
+     */
+    public static String vmNameToJavaName (final String vmName)
+    {
+        if (vmName == null) return null;
+        
+        return vmName.replace ('/', '.');
+    }
+    
+    /**
+     * NOTE: With 'shortTypeNames'=true the output is potentially lossy (truncates
+     * package name) and can result in method signature collisions in very rare
+     * circumstances (e.g., java.awt.List = java.util.List).<P>
+     * 
+     * Return type info is also lost.
+     * 
+     * @return method name (signature), no package prefix, no return type
+     */
+    public static String methodVMNameToJavaName (final String className,
+                                                 final String methodVMName,
+                                                 final String descriptor,
+                                                 final boolean renameInits,
+                                                 final boolean shortTypeNames,
+                                                 final boolean appendReturnType)
+    {
+        final StringBuffer out = new StringBuffer ();
+        
+        if (renameInits)
+        {
+            if (IClassDefConstants.CLINIT_NAME.equals (methodVMName))
+                return "<static initializer>";
+            else if (IClassDefConstants.INIT_NAME.equals (methodVMName))
+                out.append (className);
+            else
+                out.append (methodVMName);
+        }
+        else
+        {
+            if (IClassDefConstants.CLINIT_NAME.equals (methodVMName))
+                return IClassDefConstants.CLINIT_NAME;
+            else
+                out.append (methodVMName);
+        }
+        
+        final char [] chars = descriptor.toCharArray ();
+        int end;
+        
+        out.append (" (");
+        {
+            for (end = chars.length; chars [-- end] != ')'; );
+            
+            for (int start = 1; start < end; )
+            {
+                if (start > 1) out.append (", ");
+                start = typeDescriptorToJavaName (chars, start, shortTypeNames, out);
+            }
+        }        
+        
+        if (appendReturnType)
+        {
+            out.append ("): ");
+            
+            typeDescriptorToJavaName (chars, end + 1, shortTypeNames, out);
+        }
+        else
+        {
+            out.append (')');
+        }
+        
+        return out.toString ();
+    }
+    
+    
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+//    private static int typeSignatureToJavaName (final char [] signature, int start,
+//                                                final boolean shortTypeNames,
+//                                                final StringBuffer out)
+//    {
+//        
+//    }
+
+
+    private static int typeDescriptorToJavaName (final char [] descriptor, int start,
+                                                 final boolean shortTypeNames,
+                                                 final StringBuffer out)
+    {
+        int dims;
+        for (dims = 0; descriptor [start] == '['; ++ dims, ++ start);
+        
+        char c = descriptor [start ++]; 
+        switch (c)
+        {
+            case 'L':
+            {
+                if (shortTypeNames)
+                {
+                    int lastSlash = -1;
+                    for (int s = start; descriptor [s] != ';'; ++ s)
+                    {
+                        if (descriptor [s] == '/') lastSlash = s;
+                    }
+                    
+                    for (start = lastSlash > 0 ? lastSlash + 1 : start; descriptor [start] != ';'; ++ start)
+                    {
+                        c = descriptor [start];
+                        if (RENAME_INNER_CLASSES)
+                            out.append (c != '$' ? c : '.');
+                        else
+                            out.append (c);
+                    }                        
+                }
+                else
+                {
+                    for (; descriptor [start] != ';'; ++ start)
+                    {
+                        c = descriptor [start];
+                        out.append (c != '/' ? c : '.');
+                    }
+                }
+                
+                ++ start;
+            }
+            break;
+            
+            case 'B': out.append ("byte"); break;
+            case 'C': out.append ("char"); break;
+            case 'D': out.append ("double"); break;
+            case 'F': out.append ("float"); break;
+            case 'I': out.append ("int"); break;
+            case 'J': out.append ("long"); break;
+            case 'S': out.append ("short"); break;
+            case 'Z': out.append ("boolean"); break;
+            
+            case 'V': out.append ("void"); break;
+            
+            default:          
+                throw new IllegalStateException ("unknown type descriptor element: " + c);
+                
+        } // end of switch
+        
+        if (dims > 0)
+        {
+            out.append (' ');
+            for (int d = 0; d < dims; ++ d) out.append ("[]");
+        }
+        
+        return start;
+    }
+
+    
+    private Descriptors () {} // prevent subclassing
+    
+    
+    // note: setting this to 'true' is not 100% reliable because it is legal
+    // to have $'s in regular class names as well:
+    private static final boolean RENAME_INNER_CLASSES = false;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/Files.java b/core/java12/com/vladium/util/Files.java
new file mode 100644
index 0000000..4ef3a3e
--- /dev/null
+++ b/core/java12/com/vladium/util/Files.java
@@ -0,0 +1,338 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Files.java,v 1.1.1.1.2.1 2004/07/09 01:28:37 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Files
+{
+    // public: ................................................................
+    
+    /**
+     * No duplicate elimination.
+     * 
+     * @param atfile
+     */
+    public static String [] readFileList (final File atfile)
+        throws IOException
+    {
+        if (atfile == null) throw new IllegalArgumentException ("null input: atfile");
+        
+        List _result = null;
+        
+        BufferedReader in = null;
+        try
+        {
+            in = new BufferedReader (new FileReader (atfile), 8 * 1024); // uses default encoding
+            _result = new LinkedList ();
+            
+            for (String line; (line = in.readLine ()) != null; )
+            {
+                line = line.trim ();
+                if ((line.length () == 0) || (line.charAt (0) == '#')) continue;
+                
+                _result.add (line);
+            }
+        }
+        finally
+        {
+            if (in != null) try { in.close (); } catch (Exception ignore) {}
+        }
+        
+        if ((_result == null) || _result.isEmpty ())
+            return IConstants.EMPTY_STRING_ARRAY;
+        else
+        {
+            final String [] result = new String [_result.size ()];
+            _result.toArray (result);
+            
+            return result;
+        }
+    }
+    
+    /**
+     * Converts an array of path segments to an array of Files. The order
+     * of files follows the original order of path segments, except "duplicate"
+     * entries are removed. The definition of duplicates depends on 'canonical':
+     * <ul>
+     *  <li> if 'canonical'=true, the pathnames are canonicalized via {@link #canonicalizePathname}
+     *  before they are compared for equality
+     *  <li> if 'canonical'=false, the pathnames are compared as case-sensitive strings
+     * </ul>
+     * 
+     * Note that duplicate removal in classpaths affects ClassLoader.getResources().
+     * The first mode above makes the most sense, however the last one is what
+     * Sun's java.net.URLClassLoader appears to do. Hence the last mode might be
+     * necessary for reproducing its behavior in Sun-compatible JVMs.  
+     */    
+    public static File [] pathToFiles (final String [] path, final boolean canonical)
+    {
+        if (path == null) throw new IllegalArgumentException ("null input: path");
+        if (path.length == 0) return IConstants.EMPTY_FILE_ARRAY;
+        
+        final List /* Files */ _result = new ArrayList (path.length);
+        final Set /* String */ pathnames = new HashSet (path.length);
+        
+        final String separators = ",".concat (File.pathSeparator);
+        
+        for (int i = 0; i < path.length; ++ i)
+        {
+            String segment = path [i];
+            if (segment == null) throw new IllegalArgumentException ("null input: path[" + i + "]");
+            
+            final StringTokenizer tokenizer = new StringTokenizer (segment, separators);
+            while (tokenizer.hasMoreTokens ())
+            {
+                String pathname = tokenizer.nextToken ();
+            
+                if (canonical) pathname = canonicalizePathname (pathname);
+                
+                if (pathnames.add (pathname))
+                {
+                    _result.add (new File (pathname));
+                }
+            }
+        }
+        
+        final File [] result = new File [_result.size ()];
+        _result.toArray (result);
+        
+        return result;
+    }
+    
+    /**
+     * Converts 'pathname' into the canonical OS form. This wrapper function
+     * will return the absolute form of 'pathname' if File.getCanonicalPath() fails.
+     */
+    public static String canonicalizePathname (final String pathname)
+    {
+        if (pathname == null) throw new IllegalArgumentException ("null input: pathname");
+        
+        try
+        {
+            return new File (pathname).getCanonicalPath ();
+        }
+        catch (Exception e)
+        {
+            return new File (pathname).getAbsolutePath ();
+        }
+    }
+    
+    public static File canonicalizeFile (final File file)
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        try
+        {
+            return file.getCanonicalFile ();
+        }
+        catch (Exception e)
+        {
+            return file.getAbsoluteFile ();
+        }
+    }
+
+    /**
+     * Invariant: (getFileName (file) + getFileExtension (file)).equals (file.getName ()).
+     * 
+     * @param file File input file descriptor [must be non-null]
+     * 
+     * @return String file name without the extension [excluding '.' separator]
+     * [if 'file' does not appear to have an extension, the full name is returned].
+     * 
+     * @throws IllegalArgumentException if 'file' is null
+     */ 
+    public static String getFileName (final File file)
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        final String name = file.getName ();
+        int lastDot = name.lastIndexOf ('.');
+        if (lastDot < 0) return name;
+        
+        return name.substring (0, lastDot);
+    }
+    
+    /**
+     * Invariant: (getFileName (file) + getFileExtension (file)).equals (file.getName ()).
+     * 
+     * @param file File input file descriptor [must be non-null]
+     * 
+     * @return String extension [including '.' separator] or "" if 'file' does not
+     * appear to have an extension.
+     * 
+     * @throws IllegalArgumentException if 'file' is null
+     */
+    public static String getFileExtension (final File file)
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        final String name = file.getName ();
+        int lastDot = name.lastIndexOf ('.');
+        if (lastDot < 0) return "";
+        
+        return name.substring (lastDot);
+    }
+    
+    /**
+     * 
+     * @param dir [null is ignored]
+     * @param file [absolute overrides 'dir']
+     * @return
+     */
+    public static File newFile (final File dir, final File file)
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        if ((dir == null) || file.isAbsolute ()) return file;
+        
+        return new File (dir, file.getPath ());
+    }
+    
+    /**
+     * 
+     * @param dir [null is ignored]
+     * @param file [absolute overrides 'dir']
+     * @return
+     */
+    public static File newFile (final File dir, final String file)
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        final File fileFile  = new File (file);
+        if ((dir == null) || fileFile.isAbsolute ()) return fileFile;
+        
+        return new File (dir, file);
+    }
+    
+    /**
+     * 
+     * @param dir [null is ignored]
+     * @param file [absolute overrides 'dir']
+     * @return
+     */
+    public static File newFile (final String dir, final String file)
+    {
+        if (file == null) throw new IllegalArgumentException ("null input: file");
+        
+        final File fileFile  = new File (file);
+        if ((dir == null) || fileFile.isAbsolute ()) return fileFile;
+        
+        return new File (dir, file);
+    }
+    
+    /**
+     * Renames 'source' to 'target' [intermediate directories are created if necessary]. If
+     * 'target' exists and 'overwrite' is false, the method is a no-op. No exceptions are
+     * thrown except for when input is invalid. If the operation fails half-way it can leave
+     * some file system artifacts behind.
+     * 
+     * @return true iff the renaming was actually performed. 
+     * 
+     * @param source file descriptor [file must exist]
+     * @param target target file descriptor [an existing target may get deleted
+     * if 'overwrite' is true]
+     * @param overwrite if 'true', forces an existing target to be deleted 
+     * 
+     * @throws IllegalArgumentException if 'source' is null or file does not exist
+     * @throws IllegalArgumentException if 'target' is null
+     */     
+    public static boolean renameFile (final File source, final File target, final boolean overwrite)
+    {
+        if ((source == null) || ! source.exists ())
+            throw new IllegalArgumentException ("invalid input source: [" + source + "]");
+        if (target == null)
+            throw new IllegalArgumentException ("null input: target");
+        
+        final boolean targetExists;
+        if (! (targetExists = target.exists ()) || overwrite)
+        {
+            if (targetExists)
+            {
+                // need to delete the target first or the rename will fail:
+                target.delete (); // not checking the result here: let the rename fail later
+            }
+            else
+            {
+                // note that File.renameTo() does not create intermediate directories on demand:
+                final File targetDir = target.getParentFile ();
+                if ((targetDir != null) && ! targetDir.equals (source.getParentFile ()))
+                    targetDir.mkdirs (); // TODO: clean this up on failure?
+            }
+            
+            // note: this can fail for a number of reasons, including the target
+            // being on a different drive/file system:
+            return source.renameTo (target);
+        }
+        
+        return false;
+    }
+    
+    /**
+     * A slightly stricter version of File.createTempFile() in J2SDK 1.3: it requires
+     * that the caller provide an existing parent directory for the temp file.
+     * This defers to File.createTempFile (prefix, extension, parentDir) after
+     * normalizing 'extension'.<P>
+     * 
+     * MT-safety: if several threads use this API concurrently, the temp files
+     * created are guaranteed to get created without any collisions and correspond
+     * to files that did not exist before. However, if such a temp file is deleted
+     * at a later point, this method may reuse its file name. These MT-safety
+     * guarantees do not hold if files are created in the same directory outside
+     * of this method.
+     * 
+     * @param parentDir parent dir for the temp file [may not be null and must exist]
+     * @param prefix prefix pattern for the temp file name [only the first 3
+     * chars are guaranteed to be used]
+     * @param extension pattern for the temp file name [null is equivalient to
+     * ".tmp"; this is always normalized to start with "."; only the first 3
+     * non-"." chars are guaranteed to be used]
+     * 
+     * @return writeable temp file descriptor [incorporates 'parentDir' in its pathname]
+     * @throws IOException if a temp file could not be created
+     */
+    public static File createTempFile (final File parentDir, final String prefix, String extension)
+        throws IOException
+    {
+        if ((parentDir == null) || ! parentDir.exists ())
+            throw new IllegalArgumentException ("invalid parent directory: [" + parentDir + "]");
+        if ((prefix == null) || (prefix.length () < 3))
+            throw new IllegalArgumentException ("null or less than 3 chars long: " + prefix);
+        
+        if (extension == null) extension = ".tmp";
+        else if (extension.charAt (0) != '.') extension = ".".concat (extension);
+        
+        return File.createTempFile (prefix, extension, parentDir);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private Files () {} // prevent subclassing
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/IClassLoadStrategy.java b/core/java12/com/vladium/util/IClassLoadStrategy.java
new file mode 100644
index 0000000..d5a02b4
--- /dev/null
+++ b/core/java12/com/vladium/util/IClassLoadStrategy.java
@@ -0,0 +1,31 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IClassLoadStrategy.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ * The interface implemented by any classloader selection Strategy used
+ * with {@link ClassLoaderResolver} API.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IClassLoadStrategy
+{
+    // public: ................................................................
+    
+    /**
+     * Selects a classloader based on a given load context.
+     * 
+     * @see ClassLoaderResolver#getClassLoader()
+     */
+    ClassLoader getClassLoader (ClassLoadContext ctx);
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/IConstants.java b/core/java12/com/vladium/util/IConstants.java
new file mode 100644
index 0000000..4b661ed
--- /dev/null
+++ b/core/java12/com/vladium/util/IConstants.java
@@ -0,0 +1,31 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IConstants.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.File;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IConstants
+{
+    // public: ................................................................
+    
+    String [] EMPTY_STRING_ARRAY = new String [0];
+    File [] EMPTY_FILE_ARRAY = new File [0];
+    int [] EMPTY_INT_ARRAY = new int [0];
+    
+    String EOL = System.getProperty ("line.separator", "\n");
+    
+    String INDENT_INCREMENT = "  ";
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/IPathEnumerator.java b/core/java12/com/vladium/util/IPathEnumerator.java
new file mode 100644
index 0000000..8f5c8bc
--- /dev/null
+++ b/core/java12/com/vladium/util/IPathEnumerator.java
@@ -0,0 +1,334 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IPathEnumerator.java,v 1.1.1.1.2.1 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import com.vladium.logging.Logger;
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IPathEnumerator
+{
+    // public: ................................................................
+    
+    // TODO: archives inside archives? (.war ?)
+    
+    public static interface IPathHandler
+    {
+        void handleDirStart (File pathDir, File dir); // not generated for path dirs themselves
+        void handleFile (File pathDir, File file); 
+        void handleDirEnd (File pathDir, File dir);
+
+        /**
+         * Called just after the enumerator's zip input stream for this archive
+         * is opened and the manifest entry is read.
+         */        
+        void handleArchiveStart (File parentDir, File archive, Manifest manifest);
+        
+        void handleArchiveEntry (JarInputStream in, ZipEntry entry);
+        
+        /**
+         * Called after the enumerator's zip input stream for this archive
+         * has been closed.
+         */
+        void handleArchiveEnd (File parentDir, File archive);
+        
+    } // end of nested interface
+    
+    
+    void enumerate () throws IOException;
+
+    
+    public static abstract class Factory
+    {
+        public static IPathEnumerator create (final File [] path, final boolean canonical, final IPathHandler handler)
+        {
+            return new PathEnumerator (path, canonical, handler);
+        }
+        
+        private static final class PathEnumerator implements IPathEnumerator
+        {
+            public void enumerate () throws IOException
+            {
+                final IPathHandler handler = m_handler;
+                
+                for (m_pathIndex = 0; m_pathIndex < m_path.size (); ++ m_pathIndex) // important not to cache m_path.size()
+                {
+                    final File f = (File) m_path.get (m_pathIndex);
+                    
+                    if (! f.exists ())
+                    {
+                        if (IGNORE_INVALID_ENTRIES)
+                            continue;
+                        else
+                            throw new IllegalArgumentException ("path entry does not exist: [" + f + "]");
+                    }
+                    
+                    
+                    if (f.isDirectory ())
+                    {
+                        if (m_verbose) m_log.verbose ("processing dir path entry [" + f.getAbsolutePath () + "] ...");
+                        
+                        m_currentPathDir = f;
+                        enumeratePathDir (null);
+                    }
+                    else
+                    {
+                        final String name = f.getName ();
+                        final String lcName = name.toLowerCase ();
+                        
+                        if (lcName.endsWith (".zip") || lcName.endsWith (".jar"))
+                        {
+                            if (m_verbose) m_log.verbose ("processing archive path entry [" + f.getAbsolutePath () + "] ...");
+                            
+                            final File parent = f.getParentFile (); // could be null
+                            final File archive = new File (name);
+                            m_currentPathDir = parent;
+                        
+                            // move to enumeratePathArchive(): handler.handleArchiveStart (parent, archive);
+                            enumeratePathArchive (name);
+                            handler.handleArchiveEnd (parent, archive); // note: it is important that this is called after the zip stream has been closed
+                        }
+                        else if (! IGNORE_INVALID_ENTRIES)
+                        {
+                            throw new IllegalArgumentException ("path entry is not a directory or an archive: [" + f + "]");
+                        }
+                    }
+                }
+            }
+            
+            PathEnumerator (final File [] path, final boolean canonical, final IPathHandler handler)
+            {
+                m_path = new ArrayList (path.length);
+                for (int p = 0; p < path.length; ++ p) m_path.add (path [p]);
+                
+                m_canonical = canonical;
+                
+                if (handler == null) throw new IllegalArgumentException ("null input: handler");
+                m_handler = handler;
+                
+                m_processManifest = true; // TODO
+                
+                if (m_processManifest)
+                {
+                    m_pathSet = new HashSet (path.length);
+                    for (int p = 0; p < path.length; ++ p)
+                    {
+                        m_pathSet.add (path [p].getPath ()); // set of [possibly canonical] paths
+                    }
+                }
+                else
+                {
+                    m_pathSet = null;
+                }
+                
+                m_log = Logger.getLogger (); // each path enumerator caches its logger at creation time
+                m_verbose = m_log.atVERBOSE ();
+                m_trace1 = m_log.atTRACE1 ();
+            }
+            
+            
+            private void enumeratePathDir (final String dir)
+                throws IOException
+            {
+                final boolean trace1 = m_trace1;
+                
+                final File currentPathDir = m_currentPathDir;
+                final File fullDir = dir != null ? new File (currentPathDir, dir) : currentPathDir;
+                
+                final String [] children = fullDir.list ();
+                final IPathHandler handler = m_handler;
+                
+                for (int c = 0, cLimit = children.length; c < cLimit; ++ c)
+                {
+                    final String childName = children [c];
+                    
+                    final File child = dir != null ? new File (dir, childName) : new File (childName);
+                    final File fullChild = new File (fullDir, childName);
+                    
+                    if (fullChild.isDirectory ())
+                    {
+                        handler.handleDirStart (currentPathDir, child);
+                        if (trace1) m_log.trace1 ("enumeratePathDir", "recursing into [" + child.getName () + "] ...");
+                        enumeratePathDir (child.getPath ());
+                        handler.handleDirEnd (currentPathDir, child);
+                    }
+                    else
+                    {
+//                        final String lcName = childName.toLowerCase ();
+//                        
+//                        if (lcName.endsWith (".zip") || lcName.endsWith (".jar"))
+//                        {
+//                            handler.handleArchiveStart (currentPathDir, child);
+//                            enumeratePathArchive (child.getPath ());
+//                            handler.handleArchiveEnd (currentPathDir, child);
+//                        }
+//                        else
+                        {
+                            if (trace1) m_log.trace1 ("enumeratePathDir", "processing file [" + child.getName () + "] ...");
+                            handler.handleFile (currentPathDir, child);
+                        }
+                    }
+                }
+            }
+            
+            private void enumeratePathArchive (final String archive)
+                throws IOException
+            {
+                final boolean trace1 = m_trace1;
+                
+                final File fullArchive = new File (m_currentPathDir, archive);
+                
+                JarInputStream in = null;
+                try
+                {
+                    // note: Sun's JarFile uses native code and has been known to 
+                    // crash the JVM in some builds; however, it uses random file
+                    // access and can find "bad" manifests that are not the first
+                    // entries in their archives (which JarInputStream can't do);
+                    // [bugs: 4263225, 4696354, 4338238]
+                    //
+                    // there is really no good solution here but as a compromise
+                    // I try to read the manifest again via a JarFile if the stream
+                    // returns null for it:
+                    
+                    in = new JarInputStream (new BufferedInputStream (new FileInputStream (fullArchive), 32 * 1024));
+                    
+                    final IPathHandler handler = m_handler;
+                    
+                    Manifest manifest = in.getManifest (); // can be null
+                    if (manifest == null) manifest = readManifestViaJarFile (fullArchive); // can be null 
+                    
+                    handler.handleArchiveStart (m_currentPathDir, new File (archive), manifest);
+                    
+                    // note: this loop does not skip over the manifest-related
+                    // entries [the handler needs to be smart about that]
+                    for (ZipEntry entry; (entry = in.getNextEntry ()) != null; )
+                    {
+                        // TODO: handle nested archives
+                        
+                        if (trace1) m_log.trace1 ("enumeratePathArchive", "processing archive entry [" + entry.getName () + "] ...");
+                        handler.handleArchiveEntry (in, entry);
+                        in.closeEntry ();
+                    }
+                    
+                    
+                    // TODO: this needs major testing
+                    if (m_processManifest)
+                    {
+                        // note: JarInputStream only reads the manifest if it the
+                        // first jar entry
+                        if (manifest == null) manifest = in.getManifest ();
+                        if (manifest != null)
+                        {
+                            final Attributes attributes = manifest.getMainAttributes ();
+                            if (attributes != null)
+                            {
+                                // note: Sun's documentation says that multiple Class-Path:
+                                // entries are merged sequentially (http://java.sun.com/products/jdk/1.2/docs/guide/extensions/spec.html)
+                                // however, their own code does not implement this 
+                                final String jarClassPath = attributes.getValue (Attributes.Name.CLASS_PATH);
+                                if (jarClassPath != null)
+                                {
+                                    final StringTokenizer tokenizer = new StringTokenizer (jarClassPath);
+                                    for (int p = 1; tokenizer.hasMoreTokens (); )
+                                    {
+                                        final String relPath = tokenizer.nextToken ();
+                                        
+                                        final File archiveParent = fullArchive.getParentFile ();
+                                        final File path = archiveParent != null ? new File (archiveParent, relPath) : new File (relPath);
+                                        
+                                        final String fullPath = m_canonical ? Files.canonicalizePathname (path.getPath ()) : path.getPath ();
+                                        
+                                        if (m_pathSet.add (fullPath))
+                                        {
+                                            if (m_verbose) m_log.verbose ("  added manifest Class-Path entry [" + path + "]");
+                                            m_path.add (m_pathIndex + (p ++), path); // insert after the current m_path entry
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                catch (FileNotFoundException fnfe) // ignore: this should not happen
+                {
+                    if ($assert.ENABLED) throw fnfe;
+                }
+                finally
+                {
+                    if (in != null) try { in.close (); } catch (Exception ignore) {}
+                }
+            }
+            
+            
+            // see comments at the start of enumeratePathArchive()
+            
+            private static Manifest readManifestViaJarFile (final File archive)
+            {
+                Manifest result = null;
+                
+                JarFile jarfile = null;
+                try
+                {
+                    jarfile = new JarFile (archive, false); // 3-arg constructor is not in J2SE 1.2
+                    result = jarfile.getManifest ();
+                }
+                catch (IOException ignore)
+                {
+                }
+                finally
+                {
+                    if (jarfile != null) try { jarfile.close (); } catch (IOException ignore) {} 
+                }
+                
+                return result;
+            } 
+            
+
+            private final ArrayList /* File */ m_path;
+            private final boolean m_canonical;
+            private final Set /* String */ m_pathSet;
+            private final IPathHandler m_handler;
+            private final boolean m_processManifest;
+            
+            private final Logger m_log;
+            private boolean m_verbose, m_trace1;
+            
+            private int m_pathIndex;
+            private File m_currentPathDir;
+            
+            // if 'true', non-existent or non-archive or non-directory path entries
+            // will be silently ignored: 
+            private static final boolean IGNORE_INVALID_ENTRIES = true; // this is consistent with the normal JVM behavior
+                        
+        } // end of nested class
+        
+    } // end of nested class
+        
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/IProperties.java b/core/java12/com/vladium/util/IProperties.java
new file mode 100644
index 0000000..09bb84e
--- /dev/null
+++ b/core/java12/com/vladium/util/IProperties.java
@@ -0,0 +1,370 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IProperties.java,v 1.1.1.1.2.1 2004/07/08 10:52:10 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IProperties
+{
+    // public: ................................................................
+    
+    /**
+     * An IMapper is a stateless hook for mapping a arbitrary property key
+     * to another (useful, for example, for property aliasing and defaulting).
+     * Each IMapper must be completely stateless and could be shared between
+     * multiple IProperties instances (and invoked from multiple concurrent threads).
+     */
+    interface IMapper
+    {
+        String getMappedKey (final String key);
+        
+    } // end of nested interface
+    
+    
+    String getProperty (String key);
+    String getProperty (String key, String dflt);
+    boolean isOverridden (String key);
+    
+    IProperties copy ();
+    Iterator /* String */ properties ();
+    Properties toProperties ();
+    /**
+     * @param prefix [may not be null]
+     */
+    String [] toAppArgsForm (final String prefix);
+    boolean isEmpty ();
+    void list (PrintStream out);
+    void list (PrintWriter out);
+
+    String setProperty (String key, String value);
+    
+    
+    abstract class Factory
+    {
+        /**
+         * Creates an empty IProperties set with an optional property mapper.
+         * 
+         * @param mapper [may be null]
+         * @return an empty property set [never null]
+         */
+        public static IProperties create (final IMapper mapper)
+        {
+            return new PropertiesImpl (null, mapper);
+        }
+        
+        /**
+         * Converts a java.util.Properties instance to an IProperties instance,
+         * with an optional property mapper. Note that 'properties' content is
+         * shallowly cloned, so the result can be mutated independently from
+         * the input.
+         * 
+         * @param properties [may not be null]
+         * @param mapper [may be null]
+         * @return a property set based on 'properties' [never null]
+         */
+        public static IProperties wrap (final Properties properties, final IMapper mapper)
+        {
+            final HashMap map = new HashMap ();
+            
+            // always use propertyNames() for traversing java.util.Properties:
+            
+            for (Enumeration names = properties.propertyNames (); names.hasMoreElements (); )
+            {
+                final String n = (String) names.nextElement ();
+                final String v = properties.getProperty (n);
+                
+                map.put (n, v);
+            }
+            
+            return new PropertiesImpl (map, mapper); // note: map is a defensive clone
+        }
+
+        /**
+         * Combines two property sets by creating a property set that chains 'overrides'
+         * to 'base' for property delegation. Note that 'overrides' are cloned
+         * defensively and so the result can be mutated independently of both inputs.
+         * 
+         * @param overrides [may be null]
+         * @param base [may be null]
+         * @return [never null; an empty property set with a null mapper is created
+         * if both inputs are null]
+         */
+        public static IProperties combine (final IProperties overrides, final IProperties base)
+        {
+            final IProperties result = overrides != null
+                ? overrides.copy ()
+                : create (null);
+            
+            // [assertion: result != null]
+
+            ((PropertiesImpl) result).getLastProperties ().setDelegate ((PropertiesImpl) base);
+            
+            return result;
+        }
+        
+        /*
+         * Not MT-safe
+         */
+        private static final class PropertiesImpl implements IProperties, Cloneable
+        {
+            // ACCESSORS (IProperties):
+            
+            public String getProperty (final String key)
+            {
+                return getProperty (key, null);
+            }
+            
+            public String getProperty (final String key, final String dflt)
+            {
+                String value = (String) m_valueMap.get (key);
+                
+                // first, try to delegate horizontally:
+                if ((value == null) && (m_mapper != null))
+                {
+                    final String mappedKey = m_mapper.getMappedKey (key);
+                    
+                    if (mappedKey != null)
+                        value = (String) m_valueMap.get (mappedKey); 
+                }
+                
+                // next, try to delegate vertically:
+                if ((value == null) && (m_delegate != null))
+                {
+                    value = m_delegate.getProperty (key, null); 
+                }
+                
+                return value != null ? value : dflt;
+            }
+            
+            public boolean isOverridden (final String key)
+            {
+                return m_valueMap.containsKey (key);
+            }
+            
+            public IProperties copy ()
+            {
+                final PropertiesImpl _clone;
+                try
+                {
+                    _clone = (PropertiesImpl) super.clone ();
+                }
+                catch (CloneNotSupportedException cnse)
+                {
+                    throw new Error (cnse.toString ()); // never happens
+                }
+
+                _clone.m_valueMap = (HashMap) m_valueMap.clone ();
+                _clone.m_unmappedKeySet = null;
+                
+                // note: m_mapper field is not cloned by design (mappers are stateless/shareable)
+                
+                PropertiesImpl scan = _clone;
+                for (PropertiesImpl delegate = m_delegate; delegate != null; delegate = delegate.m_delegate)
+                {
+                    final PropertiesImpl _delegateClone;
+                    try
+                    {
+                        _delegateClone = (PropertiesImpl) delegate.clone ();
+                    }
+                    catch (CloneNotSupportedException cnse)
+                    {
+                        throw new Error (cnse.toString ()); // never happens
+                    }
+                    
+                    // note that the value map needs to be cloned not only for the
+                    // outermost IProperties set but for the inner ones as well
+                    // (to prevent independent mutation in them from showing through)
+                    
+                    _delegateClone.m_valueMap = (HashMap) delegate.m_valueMap.clone ();
+                    _delegateClone.m_unmappedKeySet = null; // ensure that the last delegate will have this field reset
+                    
+                    scan.setDelegate (_delegateClone);
+                    scan = _delegateClone;
+                }
+                
+                return _clone;
+            }
+            
+            public Iterator /* String */ properties ()
+            {
+                return unmappedKeySet ().iterator ();
+            }
+            
+            public Properties toProperties ()
+            {
+                final Properties result = new Properties ();
+                
+                for (Iterator i = properties (); i.hasNext (); )
+                {
+                    final String n = (String) i.next ();
+                    final String v = getProperty (n);
+                    
+                    result.setProperty (n, v);
+                }
+                
+                return result;
+            }
+            
+            public boolean isEmpty ()
+            {
+                return m_valueMap.isEmpty () && ((m_delegate == null) || ((m_delegate != null) && m_delegate.isEmpty ()));
+            }
+            
+            public String [] toAppArgsForm (final String prefix)
+            {
+                if (isEmpty ())
+                    return IConstants.EMPTY_STRING_ARRAY;
+                else
+                {
+                    if (prefix == null)
+                        throw new IllegalArgumentException ("null input: prefix");
+                    
+                    final List _result = new ArrayList ();
+                    for (Iterator names = properties (); names.hasNext (); )
+                    {
+                        final String name = (String) names.next ();
+                        final String value = getProperty (name, "");
+                        
+                        _result.add (prefix.concat (name).concat ("=").concat (value)); 
+                    }
+                    
+                    final String [] result = new String [_result.size ()];
+                    _result.toArray (result);
+                    
+                    return result;
+                }
+            }    
+
+            public void list (final PrintStream out)
+            {
+                if (out != null)
+                {
+                    for (Iterator i = properties (); i.hasNext (); )
+                    {
+                        final String n = (String) i.next ();
+                        final String v = getProperty (n);
+                        
+                        out.println (n + ":\t[" + v + "]");
+                    }
+                }
+            }
+            
+            public void list (final PrintWriter out)
+            {
+                if (out != null)
+                {
+                    for (Iterator i = properties (); i.hasNext (); )
+                    {
+                        final String n = (String) i.next ();
+                        final String v = getProperty (n);
+                        
+                        out.println (n + ":\t[" + v + "]");
+                    }
+                }
+            }
+            
+            // MUTATORS:
+            
+            public String setProperty (final String key, final String value)
+            {
+                if (value == null)
+                    throw new IllegalArgumentException ("null input: value");
+                
+                m_unmappedKeySet = null;
+                
+                return (String) m_valueMap.put (key, value); 
+            }
+            
+            
+            PropertiesImpl (final HashMap values, final IMapper mapper)
+            {
+                m_mapper = mapper;
+                m_valueMap = values != null ? values : new HashMap ();
+                
+                m_delegate = null;
+            }
+            
+            
+            Set unmappedKeySet ()
+            {
+                Set result = m_unmappedKeySet;
+                if (result == null)
+                {
+                    result = new TreeSet ();
+                    result.addAll (m_valueMap.keySet ());
+                    if (m_delegate != null)
+                        result.addAll (m_delegate.unmappedKeySet ());
+                    
+                    m_unmappedKeySet = result;
+                    return result;
+                }
+                
+                return result;
+            }
+            
+            // ACCESSORS:
+            
+            PropertiesImpl getLastProperties ()
+            {
+                PropertiesImpl result = this;
+                
+                for (PropertiesImpl delegate = m_delegate; delegate != null; delegate = delegate.m_delegate)
+                {
+                    // this does not detect all possible cycles:
+                    if (delegate == this)
+                        throw new IllegalStateException ("cyclic delegation detected");
+                    
+                    result = delegate;
+                }
+                
+                return result;
+            }
+            
+            // MUTATORS:
+            
+            void setDelegate (final PropertiesImpl delegate)
+            {
+                m_delegate = delegate;
+                
+                m_unmappedKeySet = null;
+            }
+            
+            
+            private final IMapper m_mapper;
+            private /*final*/ HashMap m_valueMap; // never null
+            
+            private PropertiesImpl m_delegate;
+            private transient Set m_unmappedKeySet;
+            
+        } // end of nested class
+        
+    } // end of nested class
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/IntIntMap.java b/core/java12/com/vladium/util/IntIntMap.java
new file mode 100644
index 0000000..7abe824
--- /dev/null
+++ b/core/java12/com/vladium/util/IntIntMap.java
@@ -0,0 +1,330 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IntIntMap.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ *
+ * MT-safety: an instance of this class is <I>not</I> safe for access from
+ * multiple concurrent threads [even if access is done by a single thread at a
+ * time]. The caller is expected to synchronize externally on an instance [the
+ * implementation does not do internal synchronization for the sake of efficiency].
+ * java.util.ConcurrentModificationException is not supported either.
+ *
+ * @author Vlad Roubtsov, (C) 2001
+ */
+public
+final class IntIntMap
+{
+    // public: ................................................................
+    
+    // TODO: optimize key comparisons using key.hash == entry.key.hash condition
+
+    /**
+     * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
+     */
+    public IntIntMap ()
+    {
+        this (11, 0.75F);
+    }
+    
+    /**
+     * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
+     */
+    public IntIntMap (final int initialCapacity)
+    {
+        this (initialCapacity, 0.75F);
+    }
+    
+    /**
+     * Constructs an IntObjectMap with specified initial capacity and load factor.
+     *
+     * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
+     * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
+     */
+    public IntIntMap (int initialCapacity, final float loadFactor)
+    {
+        if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
+        if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
+            throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
+        
+        if (initialCapacity == 0) initialCapacity = 1;
+        
+        m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;        
+        m_sizeThreshold = (int) (initialCapacity * loadFactor);
+        m_buckets = new Entry [initialCapacity];
+    }
+    
+    
+    /**
+     * Overrides Object.toString() for debug purposes.
+     */
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ();
+        debugDump (s);
+        
+        return s.toString ();
+    }
+    
+    /**
+     * Returns the number of key-value mappings in this map.
+     */
+    public int size ()
+    {
+        return m_size;
+    }
+
+    public boolean contains (final int key)
+    {
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key) return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Returns the value that is mapped to a given 'key'. Returns
+     * false if this key has never been mapped.
+     *
+     * @param key mapping key
+     * @param out holder for the found value [must be at least of size 1]
+     *
+     * @return 'true' if this key was mapped to an existing value
+     */
+    public boolean get (final int key, final int [] out)
+    {
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+            {
+                out [0] = entry.m_value;
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public boolean get (final int key, final int [] out, final int index)
+    {
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+            {
+                out [index] = entry.m_value;
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public int [] keys ()
+    {
+        final int [] result = new int [m_size];
+        int scan = 0;
+        
+        for (int b = 0; b < m_buckets.length; ++ b)
+        {
+            for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
+            {
+                result [scan ++] = entry.m_key;
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
+     *
+     * @param key mapping key
+     * @param value mapping value
+     */
+    public void put (final int key, final int value)
+    {
+        Entry currentKeyEntry = null;
+        
+        // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
+        
+        // index into the corresponding hash bucket:
+        int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        Entry [] buckets = m_buckets;
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+            {
+                currentKeyEntry = entry;
+                break;
+            }
+        }
+        
+        if (currentKeyEntry != null)
+        {
+            // replace the current value:
+                
+            currentKeyEntry.m_value = value;
+        }
+        else
+        {
+            // add a new entry:
+            
+            if (m_size >= m_sizeThreshold) rehash ();
+            
+            buckets = m_buckets;
+            bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+            final Entry bucketListHead = buckets [bucketIndex];
+            final Entry newEntry = new Entry (key, value, bucketListHead);
+            buckets [bucketIndex] = newEntry;
+            
+            ++ m_size;
+        }
+    }
+    
+    /**
+     * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
+     *
+     * @param key mapping key
+     */
+    public void remove (final int key)
+    {
+        // index into the corresponding hash bucket:
+        final int bucketIndex = (key  & 0x7FFFFFFF) % m_buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        Entry [] buckets = m_buckets;
+        for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )
+        {
+            final Entry next = entry.m_next;
+            
+            if (key == entry.m_key)
+            {
+                if (prev == entry)
+                    buckets [bucketIndex] = next;
+                else
+                    prev.m_next = next;
+                
+                -- m_size;     
+                break;
+            }
+            
+            prev = entry;
+            entry = next;
+        }
+    }
+
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    void debugDump (final StringBuffer out)
+    {
+        if (out != null)
+        {
+            out.append (super.toString ()); out.append (EOL);
+            out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
+            out.append ("size threshold = " + m_sizeThreshold + EOL);
+        }
+    }
+
+    // private: ...............................................................
+
+    
+    /**
+     * The structure used for chaining colliding keys.
+     */
+    private static final class Entry
+    {
+        Entry (final int key, final int value, final Entry next)
+        {
+            m_key = key; 
+            m_value = value;
+            m_next = next;
+        }
+        
+        int m_key;
+        int m_value;
+        
+        Entry m_next; // singly-linked list link
+        
+    } // end of nested class
+    
+
+    /**
+     * Re-hashes the table into a new array of buckets.
+     */
+    private void rehash ()
+    {
+        // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
+        // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
+        // only grow in size
+        
+        final Entry [] buckets = m_buckets;
+        
+        final int newBucketCount = (m_buckets.length << 1) + 1;
+        final Entry [] newBuckets = new Entry [newBucketCount];
+
+        // rehash all entry chains in every bucket:
+        for (int b = 0; b < buckets.length; ++ b)
+        {
+            for (Entry entry = buckets [b]; entry != null; )
+            {
+                final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
+                final int entryKeyHash = entry.m_key & 0x7FFFFFFF;
+            
+                // index into the corresponding new hash bucket:
+                final int newBucketIndex = entryKeyHash % newBucketCount;
+                
+                final Entry bucketListHead = newBuckets [newBucketIndex];
+                entry.m_next = bucketListHead;
+                newBuckets [newBucketIndex] = entry;                                
+                
+                entry = next;
+            }
+        }
+        
+
+        m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
+        m_buckets = newBuckets;
+    }
+    
+    
+    private final float m_loadFactor; // determines the setting of m_sizeThreshold
+    
+    private Entry [] m_buckets; // table of buckets
+    private int m_size; // number of keys in the table, not cleared as of last check
+    private int m_sizeThreshold; // size threshold for rehashing
+        
+    private static final String EOL = System.getProperty ("line.separator", "\n");
+    
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/util/IntObjectMap.java b/core/java12/com/vladium/util/IntObjectMap.java
new file mode 100644
index 0000000..6cc6e70
--- /dev/null
+++ b/core/java12/com/vladium/util/IntObjectMap.java
@@ -0,0 +1,288 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IntObjectMap.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.Serializable;
+
+// ----------------------------------------------------------------------------
+/**
+ *
+ * MT-safety: an instance of this class is <I>not</I> safe for access from
+ * multiple concurrent threads [even if access is done by a single thread at a
+ * time]. The caller is expected to synchronize externally on an instance [the
+ * implementation does not do internal synchronization for the sake of efficiency].
+ * java.util.ConcurrentModificationException is not supported either.
+ *
+ * @author Vlad Roubtsov, (C) 2001
+ */
+public
+final class IntObjectMap implements Serializable
+{
+    // public: ................................................................
+
+    /**
+     * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
+     */
+    public IntObjectMap ()
+    {
+        this (11, 0.75F);
+    }
+    
+    /**
+     * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
+     */
+    public IntObjectMap (final int initialCapacity)
+    {
+        this (initialCapacity, 0.75F);
+    }
+    
+    /**
+     * Constructs an IntObjectMap with specified initial capacity and load factor.
+     *
+     * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
+     * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
+     */
+    public IntObjectMap (int initialCapacity, final float loadFactor)
+    {
+        if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
+        if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
+            throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
+        
+        if (initialCapacity == 0) initialCapacity = 1;
+        
+        m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;        
+        m_sizeThreshold = (int) (initialCapacity * loadFactor);
+        m_buckets = new Entry [initialCapacity];
+    }
+    
+    
+    /**
+     * Overrides Object.toString() for debug purposes.
+     */
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ();
+        debugDump (s);
+        
+        return s.toString ();
+    }
+    
+    /**
+     * Returns the number of key-value mappings in this map.
+     */
+    public int size ()
+    {
+        return m_size;
+    }
+
+    public boolean contains (final int key)
+    {
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+                return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Returns the value that is mapped to a given 'key'. Returns
+     * null if (a) this key has never been mapped or (b) it has been
+     * mapped to a null value.
+     *
+     * @param key mapping key
+     *
+     * @return Object value mapping for 'key' [can be null].
+     */
+    public Object get (final int key)
+    {
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+                return entry.m_value;
+        }
+        
+        return null;
+    }
+    
+    public int [] keys ()
+    {
+        if (m_size == 0)
+            return IConstants.EMPTY_INT_ARRAY;
+        else
+        {
+            final int [] result = new int [m_size];
+            int scan = 0;
+            
+            for (int b = 0; b < m_buckets.length; ++ b)
+            {
+                for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
+                {
+                    result [scan ++] = entry.m_key;
+                }
+            }
+            
+            return result;
+        }
+    }
+    
+    /**
+     * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
+     *
+     * @param key mapping key
+     * @param value mapping value [can be null].
+     *
+     * @return Object previous value mapping for 'key' [can be null]
+     */
+    public Object put (final int key, final Object value)
+    {
+        Entry currentKeyEntry = null;
+        
+        // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
+        
+        // index into the corresponding hash bucket:
+        int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        Entry [] buckets = m_buckets;
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+            {
+                currentKeyEntry = entry;
+                break;
+            }
+        }
+        
+        if (currentKeyEntry != null)
+        {
+            // replace the current value:
+                
+            final Object currentKeyValue = currentKeyEntry.m_value;
+            currentKeyEntry.m_value = value;
+            
+            return currentKeyValue;
+        }
+        else
+        {
+            // add a new entry:
+            
+            if (m_size >= m_sizeThreshold) rehash ();
+            
+            buckets = m_buckets;
+            bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+            final Entry bucketListHead = buckets [bucketIndex];
+            final Entry newEntry = new Entry (key, value, bucketListHead);
+            buckets [bucketIndex] = newEntry;
+            
+            ++ m_size;
+            
+            return null;
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    void debugDump (final StringBuffer out)
+    {
+        if (out != null)
+        {
+            out.append (super.toString ()); out.append (EOL);
+            out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
+            out.append ("size threshold = " + m_sizeThreshold + EOL);
+        }
+    }
+
+    // private: ...............................................................
+
+    
+    /**
+     * The structure used for chaining colliding keys.
+     */
+    private static final class Entry implements Serializable
+    {
+        Entry (final int key, final Object value, final Entry next)
+        {
+            m_key = key; 
+            m_value = value;
+            m_next = next;
+        }
+        
+        Object m_value;           // reference to the value [never null]
+        final int m_key;
+        
+        Entry m_next; // singly-linked list link
+        
+    } // end of nested class
+    
+
+    /**
+     * Re-hashes the table into a new array of buckets.
+     */
+    private void rehash ()
+    {
+        // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
+        // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
+        // only grow in size
+        
+        final Entry [] buckets = m_buckets;
+        
+        final int newBucketCount = (m_buckets.length << 1) + 1;
+        final Entry [] newBuckets = new Entry [newBucketCount];
+
+        // rehash all entry chains in every bucket:
+        for (int b = 0; b < buckets.length; ++ b)
+        {
+            for (Entry entry = buckets [b]; entry != null; )
+            {
+                final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
+                final int entryKey = entry.m_key;
+            
+                // index into the corresponding new hash bucket:
+                final int newBucketIndex = (entryKey & 0x7FFFFFFF) % newBucketCount;
+                
+                final Entry bucketListHead = newBuckets [newBucketIndex];
+                entry.m_next = bucketListHead;
+                newBuckets [newBucketIndex] = entry;                                
+                
+                entry = next;
+            }
+        }
+        
+
+        m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
+        m_buckets = newBuckets;
+    }
+    
+    
+    private final float m_loadFactor; // determines the setting of m_sizeThreshold
+    
+    private Entry [] m_buckets; // table of buckets
+    private int m_size; // number of keys in the table, not cleared as of last check
+    private int m_sizeThreshold; // size threshold for rehashing
+        
+    private static final String EOL = System.getProperty ("line.separator", "\n");
+    
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/util/IntSet.java b/core/java12/com/vladium/util/IntSet.java
new file mode 100644
index 0000000..8cf2d99
--- /dev/null
+++ b/core/java12/com/vladium/util/IntSet.java
@@ -0,0 +1,260 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IntSet.java,v 1.1.1.1 2004/05/09 16:57:53 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ *
+ * MT-safety: an instance of this class is <I>not</I> safe for access from
+ * multiple concurrent threads [even if access is done by a single thread at a
+ * time]. The caller is expected to synchronize externally on an instance [the
+ * implementation does not do internal synchronization for the sake of efficiency].
+ * java.util.ConcurrentModificationException is not supported either.
+ *
+ * @author Vlad Roubtsov, (C) 2001
+ */
+public
+final class IntSet
+{
+    // public: ................................................................
+
+    /**
+     * Equivalent to <CODE>IntSet(11, 0.75F)</CODE>.
+     */
+    public IntSet ()
+    {
+        this (11, 0.75F);
+    }
+    
+    /**
+     * Equivalent to <CODE>IntSet(capacity, 0.75F)</CODE>.
+     */
+    public IntSet (final int initialCapacity)
+    {
+        this (initialCapacity, 0.75F);
+    }
+    
+    /**
+     * Constructs an IntSet with specified initial capacity and load factor.
+     *
+     * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
+     * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
+     */
+    public IntSet (int initialCapacity, final float loadFactor)
+    {
+        if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
+        if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
+            throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
+        
+        if (initialCapacity == 0) initialCapacity = 1;
+        
+        m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;        
+        m_sizeThreshold = (int) (initialCapacity * loadFactor);
+        m_buckets = new Entry [initialCapacity];
+    }
+    
+    
+    /**
+     * Overrides Object.toString() for debug purposes.
+     */
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ();
+        debugDump (s);
+        
+        return s.toString ();
+    }
+    
+    /**
+     * Returns the number of key-value mappings in this map.
+     */
+    public int size ()
+    {
+        return m_size;
+    }
+    
+    public boolean contains (final int key)
+    {
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+                return true;
+        }
+        
+        return false;
+    }
+    
+    public int [] values ()
+    {
+        if (m_size == 0)
+            return IConstants.EMPTY_INT_ARRAY;
+        else
+        {
+            final int [] result = new int [m_size];
+            int scan = 0;
+            
+            for (int b = 0; b < m_buckets.length; ++ b)
+            {
+                for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
+                {
+                    result [scan ++] = entry.m_key;
+                }
+            }
+            
+            return result;
+        }
+    }
+    
+    public void values (final int [] target, final int offset)
+    {
+        if (m_size != 0)
+        {
+            int scan = offset;
+            
+            for (int b = 0; b < m_buckets.length; ++ b)
+            {
+                for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
+                {
+                    target [scan ++] = entry.m_key;
+                }
+            }
+        }
+    }
+    
+    public boolean add (final int key)
+    {
+        Entry currentKeyEntry = null;
+        
+        // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
+        
+        // index into the corresponding hash bucket:
+        int bucketIndex = (key & 0x7FFFFFFF) % m_buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        Entry [] buckets = m_buckets;
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if (key == entry.m_key)
+            {
+                currentKeyEntry = entry;
+                break;
+            }
+        }
+        
+        if (currentKeyEntry == null)
+        {
+            // add a new entry:
+            
+            if (m_size >= m_sizeThreshold) rehash ();
+            
+            buckets = m_buckets;
+            bucketIndex = (key & 0x7FFFFFFF) % buckets.length;
+            final Entry bucketListHead = buckets [bucketIndex];
+            final Entry newEntry = new Entry (key, bucketListHead);
+            buckets [bucketIndex] = newEntry;
+            
+            ++ m_size;
+            
+            return true;
+        }
+        else
+            return false;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    void debugDump (final StringBuffer out)
+    {
+        if (out != null)
+        {
+            out.append (super.toString ()); out.append (EOL);
+            out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
+            out.append ("size threshold = " + m_sizeThreshold + EOL);
+        }
+    }
+
+    // private: ...............................................................
+
+    
+    /**
+     * The structure used for chaining colliding keys.
+     */
+    private static final class Entry
+    {
+        Entry (final int key, final Entry next)
+        {
+            m_key = key; 
+            m_next = next;
+        }
+        
+        final int m_key;
+        
+        Entry m_next; // singly-linked list link
+        
+    } // end of nested class
+    
+
+    /**
+     * Re-hashes the table into a new array of buckets.
+     */
+    private void rehash ()
+    {
+        // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
+        // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
+        // only grow in size
+        
+        final Entry [] buckets = m_buckets;
+        
+        final int newBucketCount = (m_buckets.length << 1) + 1;
+        final Entry [] newBuckets = new Entry [newBucketCount];
+
+        // rehash all entry chains in every bucket:
+        for (int b = 0; b < buckets.length; ++ b)
+        {
+            for (Entry entry = buckets [b]; entry != null; )
+            {
+                final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
+                final int entryKey = entry.m_key;
+            
+                // index into the corresponding new hash bucket:
+                final int newBucketIndex = (entryKey & 0x7FFFFFFF) % newBucketCount;
+                
+                final Entry bucketListHead = newBuckets [newBucketIndex];
+                entry.m_next = bucketListHead;
+                newBuckets [newBucketIndex] = entry;                                
+                
+                entry = next;
+            }
+        }
+        
+
+        m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
+        m_buckets = newBuckets;
+    }
+    
+    
+    private final float m_loadFactor; // determines the setting of m_sizeThreshold
+    
+    private Entry [] m_buckets; // table of buckets
+    private int m_size; // number of keys in the table, not cleared as of last check
+    private int m_sizeThreshold; // size threshold for rehashing
+        
+    private static final String EOL = System.getProperty ("line.separator", "\n");
+    
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/util/IntVector.java b/core/java12/com/vladium/util/IntVector.java
new file mode 100644
index 0000000..2bc4a99
--- /dev/null
+++ b/core/java12/com/vladium/util/IntVector.java
Binary files differ
diff --git a/core/java12/com/vladium/util/IntegerFactory.java b/core/java12/com/vladium/util/IntegerFactory.java
new file mode 100644
index 0000000..5608672
--- /dev/null
+++ b/core/java12/com/vladium/util/IntegerFactory.java
@@ -0,0 +1,53 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IntegerFactory.java,v 1.1.1.1 2004/05/09 16:57:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class IntegerFactory
+{
+    // public: ................................................................
+    
+    // TODO: use thread-local arena pattern to avoid synchronization ?
+    
+    public static Integer getInteger (final int value)
+    {
+        synchronized (s_values)
+        {
+            final Object _result = s_values.get (value);
+            
+            if (_result == null)
+            {
+                final Integer result = new Integer (value);
+                s_values.put (value, result);
+                
+                return result; 
+            }
+            
+            return (Integer) _result;
+        }
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private IntegerFactory () {} // prevent subclassing
+    
+    
+    private static final IntObjectMap s_values = new IntObjectMap (16661);
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/ObjectIntMap.java b/core/java12/com/vladium/util/ObjectIntMap.java
new file mode 100644
index 0000000..28eb3a0
--- /dev/null
+++ b/core/java12/com/vladium/util/ObjectIntMap.java
@@ -0,0 +1,326 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ObjectIntMap.java,v 1.1.1.1 2004/05/09 16:57:54 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import com.vladium.util.asserts.$assert;
+
+// ----------------------------------------------------------------------------
+/**
+ *
+ * MT-safety: an instance of this class is <I>not</I> safe for access from
+ * multiple concurrent threads [even if access is done by a single thread at a
+ * time]. The caller is expected to synchronize externally on an instance [the
+ * implementation does not do internal synchronization for the sake of efficiency].
+ * java.util.ConcurrentModificationException is not supported either.
+ *
+ * @author (C) 2001, Vlad Roubtsov
+ */
+public
+final class ObjectIntMap
+{
+    // public: ................................................................
+    
+    // TODO: optimize key comparisons using key.hash == entry.key.hash condition
+
+    /**
+     * Equivalent to <CODE>IntObjectMap(11, 0.75F)</CODE>.
+     */
+    public ObjectIntMap ()
+    {
+        this (11, 0.75F);
+    }
+    
+    /**
+     * Equivalent to <CODE>IntObjectMap(capacity, 0.75F)</CODE>.
+     */
+    public ObjectIntMap (final int initialCapacity)
+    {
+        this (initialCapacity, 0.75F);
+    }
+    
+    /**
+     * Constructs an IntObjectMap with specified initial capacity and load factor.
+     *
+     * @param initialCapacity initial number of hash buckets in the table [may not be negative, 0 is equivalent to 1].
+     * @param loadFactor the load factor to use to determine rehashing points [must be in (0.0, 1.0] range].
+     */
+    public ObjectIntMap (int initialCapacity, final float loadFactor)
+    {
+        if (initialCapacity < 0) throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
+        if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
+            throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
+        
+        if (initialCapacity == 0) initialCapacity = 1;
+        
+        m_loadFactor = loadFactor > 1.0 ? 1.0F : loadFactor;        
+        m_sizeThreshold = (int) (initialCapacity * loadFactor);
+        m_buckets = new Entry [initialCapacity];
+    }
+    
+    
+    /**
+     * Overrides Object.toString() for debug purposes.
+     */
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ();
+        debugDump (s);
+        
+        return s.toString ();
+    }
+    
+    /**
+     * Returns the number of key-value mappings in this map.
+     */
+    public int size ()
+    {
+        return m_size;
+    }
+
+    public boolean contains (final Object key)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
+        
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int keyHash = key.hashCode ();
+        final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
+                return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Returns the value that is mapped to a given 'key'. Returns
+     * false if this key has never been mapped.
+     *
+     * @param key mapping key [may not be null]
+     * @param out holder for the found value [must be at least of size 1]
+     *
+     * @return 'true' if this key was mapped to an existing value
+     */
+    public boolean get (final Object key, final int [] out)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
+        
+        // index into the corresponding hash bucket:
+        final Entry [] buckets = m_buckets;
+        final int keyHash = key.hashCode ();
+        final int bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
+            {
+                out [0] = entry.m_value;
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public Object [] keys ()
+    {
+        final Object [] result = new Object [m_size];
+        int scan = 0;
+        
+        for (int b = 0; b < m_buckets.length; ++ b)
+        {
+            for (Entry entry = m_buckets [b]; entry != null; entry = entry.m_next)
+            {
+                result [scan ++] = entry.m_key;
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
+     *
+     * @param key mapping key [may not be null]
+     * @param value mapping value.
+     */
+    public void put (final Object key, final int value)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
+        
+        Entry currentKeyEntry = null;
+        
+        // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
+        
+        // index into the corresponding hash bucket:
+        final int keyHash = key.hashCode ();
+        int bucketIndex = (keyHash & 0x7FFFFFFF) % m_buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        Entry [] buckets = m_buckets;
+        for (Entry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
+            {
+                currentKeyEntry = entry;
+                break;
+            }
+        }
+        
+        if (currentKeyEntry != null)
+        {
+            // replace the current value:
+                
+            currentKeyEntry.m_value = value;
+        }
+        else
+        {
+            // add a new entry:
+            
+            if (m_size >= m_sizeThreshold) rehash ();
+            
+            buckets = m_buckets;
+            bucketIndex = (keyHash & 0x7FFFFFFF) % buckets.length;
+            final Entry bucketListHead = buckets [bucketIndex];
+            final Entry newEntry = new Entry (key, value, bucketListHead);
+            buckets [bucketIndex] = newEntry;
+            
+            ++ m_size;
+        }
+    }
+    
+    /**
+     * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
+     *
+     * @param key mapping key [may not be null]
+     */
+    public void remove (final Object key)
+    {
+        if ($assert.ENABLED) $assert.ASSERT (key != null, "null input: key");
+        
+        // index into the corresponding hash bucket:
+        final int keyHash = key.hashCode ();
+        final int bucketIndex = (keyHash  & 0x7FFFFFFF) % m_buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        Entry [] buckets = m_buckets;
+        for (Entry entry = buckets [bucketIndex], prev = entry; entry != null; )
+        {
+            final Entry next = entry.m_next;
+            
+            if ((keyHash == entry.m_key.hashCode ()) || entry.m_key.equals (key))
+            {
+                if (prev == entry)
+                    buckets [bucketIndex] = next;
+                else
+                    prev.m_next = next;
+                
+                -- m_size;     
+                break;
+            }
+            
+            prev = entry;
+            entry = next;
+        }
+    }
+
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    void debugDump (final StringBuffer out)
+    {
+        if (out != null)
+        {
+            out.append (super.toString ()); out.append (EOL);
+            out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
+            out.append ("size threshold = " + m_sizeThreshold + EOL);
+        }
+    }
+
+    // private: ...............................................................
+
+    
+    /**
+     * The structure used for chaining colliding keys.
+     */
+    private static final class Entry
+    {
+        Entry (final Object key, final int value, final Entry next)
+        {
+            m_key = key; 
+            m_value = value;
+            m_next = next;
+        }
+        
+        Object m_key;     // reference to the value [never null]
+        int m_value;
+        
+        Entry m_next; // singly-linked list link
+        
+    } // end of nested class
+    
+
+    /**
+     * Re-hashes the table into a new array of buckets.
+     */
+    private void rehash ()
+    {
+        // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
+        // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
+        // only grow in size
+        
+        final Entry [] buckets = m_buckets;
+        
+        final int newBucketCount = (m_buckets.length << 1) + 1;
+        final Entry [] newBuckets = new Entry [newBucketCount];
+
+        // rehash all entry chains in every bucket:
+        for (int b = 0; b < buckets.length; ++ b)
+        {
+            for (Entry entry = buckets [b]; entry != null; )
+            {
+                final Entry next = entry.m_next; // remember next pointer because we are going to reuse this entry
+                final int entryKeyHash = entry.m_key.hashCode () & 0x7FFFFFFF;
+            
+                // index into the corresponding new hash bucket:
+                final int newBucketIndex = entryKeyHash % newBucketCount;
+                
+                final Entry bucketListHead = newBuckets [newBucketIndex];
+                entry.m_next = bucketListHead;
+                newBuckets [newBucketIndex] = entry;                                
+                
+                entry = next;
+            }
+        }
+        
+
+        m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
+        m_buckets = newBuckets;
+    }
+    
+    
+    private final float m_loadFactor; // determines the setting of m_sizeThreshold
+    
+    private Entry [] m_buckets; // table of buckets
+    private int m_size; // number of keys in the table, not cleared as of last check
+    private int m_sizeThreshold; // size threshold for rehashing
+        
+    private static final String EOL = System.getProperty ("line.separator", "\n");
+    
+} // end of class
+// ----------------------------------------------------------------------------
+
diff --git a/core/java12/com/vladium/util/Property.java b/core/java12/com/vladium/util/Property.java
new file mode 100644
index 0000000..814971b
--- /dev/null
+++ b/core/java12/com/vladium/util/Property.java
@@ -0,0 +1,557 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Property.java,v 1.1.1.1.2.4 2004/07/16 23:32:04 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+/*
+ * NOTE: to avoid certain build problems, this class should use only
+ * core Java APIs and not any app infrastructure.
+ */
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Property
+{
+    // public: ................................................................
+
+
+    public static boolean toBoolean (final String value)
+    {
+        if (value == null)
+            return false;
+        else
+            return value.startsWith ("t") || value.startsWith ("y");
+    }
+
+    
+    /**
+     * NOTE: this does not guarantee that the result will be mutatable
+     * independently from 'overrides' or 'base', so this method
+     * should be used for read-only property only
+     * 
+     * @param overrides [null is equivalent to empty]
+     * @param base [null is equivalent to empty]
+     * 
+     * @return [never null, could be empty]
+     */
+    public static Properties combine (final Properties overrides, final Properties base)
+    {
+        // note: no defensive copies here
+         
+        if (base == null)
+        {
+            if (overrides == null)
+                return new XProperties ();
+            else
+                return overrides;
+        }
+        
+        // [assertion: base != null]
+        
+        if (overrides == null) return base;
+        
+        // [assertion: both 'overrides' and 'base' are not null]
+        
+        final Properties result = new XProperties (base);
+        
+        // note: must use propertyNames() because that is the only method that recurses
+        // into possible bases inside 'overrides'
+        
+        for (Enumeration overrideNames = overrides.propertyNames (); overrideNames.hasMoreElements (); )
+        {
+            final String n = (String) overrideNames.nextElement ();
+            final String v = overrides.getProperty (n);
+            
+            result.setProperty (n, v);
+        }
+        
+        return result;
+    }
+    
+    /**
+     * Creates a set of properties for an application with a given namespace.
+     * This method is not property aliasing-aware.
+     * 
+     * @param namespace application namespace [may not be null]
+     * @param loader classloader to use for any classloader resource lookups
+     * [null is equivalent to the applicaton classloader]
+     * @return application properties [never null, a new instance is created
+     * on each invocation]
+     */
+    public static Properties getAppProperties (final String namespace, final ClassLoader loader)
+    {
+        if (namespace == null)
+            throw new IllegalArgumentException ("null properties: appNameLC");
+        
+        final Properties appDefaults = Property.getProperties (namespace + "_default.properties", loader);
+        final Properties systemFileOverrides;
+        {
+            final String fileName = Property.getSystemProperty (namespace + ".properties");
+            final File file = fileName != null
+                ? new File (fileName)
+                : null;
+
+            systemFileOverrides = Property.getLazyPropertiesFromFile (file);
+        }
+        final Properties systemOverrides = Property.getSystemProperties (namespace);
+        final Properties resOverrides = Property.getProperties (namespace + ".properties", loader);
+        
+        return combine (resOverrides,
+               combine (systemOverrides,
+               combine (systemFileOverrides,
+                        appDefaults)));
+    }
+    
+    public static Properties getSystemProperties (final String systemPrefix)
+    {
+        // note: this method is not synchronized on purpose
+        
+        Properties result = s_systemProperties;
+        if (result == null)
+        {
+            result = new SystemPropertyLookup (systemPrefix);
+            
+            s_systemProperties = result;
+            return result;
+        }
+        
+        return result;
+    }
+    
+    public static Properties getSystemPropertyRedirects (final Map systemRedirects)
+    {
+        // note: this method is not synchronized on purpose
+        
+        Properties result = s_systemRedirects;
+        if (result == null)
+        {
+            result = new SystemRedirectsLookup (systemRedirects);
+            
+            s_systemRedirects = result;
+            return result;
+        }
+        
+        return result;
+    }
+
+
+    public static String getSystemFingerprint ()
+    {
+        // [not synchronized intentionally]
+        
+        if (s_systemFingerprint != null)
+            return s_systemFingerprint;
+        else
+        {
+            final StringBuffer s = new StringBuffer ();
+            final char delimiter = ':';
+            
+            s.append (getSystemProperty ("java.vm.name", ""));
+            s.append (delimiter);
+            s.append (getSystemProperty ("java.vm.version", ""));
+            s.append (delimiter);
+            s.append (getSystemProperty ("java.vm.vendor", ""));
+            s.append (delimiter);
+            s.append (getSystemProperty ("os.name", ""));
+            s.append (delimiter);
+            s.append (getSystemProperty ("os.version", ""));
+            s.append (delimiter);
+            s.append (getSystemProperty ("os.arch", ""));
+            
+            s_systemFingerprint = s.toString ();
+            return s_systemFingerprint;
+        }
+    }    
+    
+    public static String getSystemProperty (final String key)
+    {
+        try
+        {
+            return System.getProperty (key);
+        }
+        catch (SecurityException se)
+        {
+            return null;
+        }
+    }
+    
+    public static String getSystemProperty (final String key, final String def)
+    {
+        try
+        {
+            return System.getProperty (key, def);
+        }
+        catch (SecurityException se)
+        {
+            return def;
+        }
+    }
+    
+    /**
+     * does not throw
+     * 
+     * @param name
+     * @return
+     */
+    public static Properties getProperties (final String name)
+    {
+        Properties result = null;
+        
+        InputStream in = null;
+        try
+        {
+            in = ResourceLoader.getResourceAsStream (name);
+            if (in != null)
+            {
+                result = new XProperties ();
+                result.load (in);
+            }
+        }
+        catch (Throwable t)
+        {
+            result = null;
+        }
+        finally
+        {
+            if (in != null) try { in.close (); } catch (Throwable ignore) {}
+            in = null;
+        }
+        
+        return result;
+    }
+    
+    /**
+     * does not throw
+     * 
+     * @param name
+     * @param loader
+     * @return
+     */
+    public static Properties getProperties (final String name, final ClassLoader loader)
+    {
+        Properties result = null;
+        
+        InputStream in = null;
+        try
+        {
+            in = ResourceLoader.getResourceAsStream (name, loader);
+            if (in != null)
+            {
+                result = new XProperties ();
+                result.load (in);
+            }
+        }
+        catch (Throwable t)
+        {
+            result = null;
+        }
+        finally
+        {
+            if (in != null) try { in.close (); } catch (Throwable ignore) {}
+            in = null;
+        }
+        
+        return result;
+    }
+
+    /**
+     * Loads 'file' as a .properties file.
+     * 
+     * @param file [may not be null]
+     * @return read properties [never null]
+     * @throws IOException on any file I/O errors
+     */
+    public static Properties getPropertiesFromFile (final File file)
+        throws IOException
+    {
+        if (file == null)
+            throw new IllegalArgumentException ("null input: file");
+        
+        Properties result = null;
+        
+        InputStream in = null;
+        try
+        {
+            in = new BufferedInputStream (new FileInputStream (file), 8 * 1024);
+
+            result = new XProperties ();
+            result.load (in);
+        }
+        finally
+        {
+            if (in != null) try { in.close (); } catch (Throwable ignore) {}
+            in = null;
+        }
+        
+        return result;
+    }    
+
+    /**
+     * Returns a lazy property implementation that will read 'load' as a .properties
+     * file on first use. If there are any file I/O errors when reading the file,
+     * they will be thrown as runtime exceptions (also on first use).
+     * 
+     * @param file [can be null, which results in an empty property set returned]
+     * @return [never null]
+     */
+    public static Properties getLazyPropertiesFromFile (final File file)
+    {
+        return new FilePropertyLookup (file);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final class FilePropertyLookup extends XProperties
+    {
+        // note: due to incredibly stupid coding in java.util.Properties
+        // (getProperty() uses a non-virtual call to get(), while propertyNames()
+        // uses a virtual call to the same instead of delegating to getProperty())
+        // I must override both methods below
+        
+        public String getProperty (final String key)
+        {
+            faultContents ();
+            
+            return m_contents.getProperty (key);
+        }
+            
+        public Object get (final Object key)
+        {
+            faultContents ();
+            
+            return m_contents.get (key);
+        }
+        
+        /*
+         * Overrides Properties.keys () [this is used for debug logging only]
+         */
+        public Enumeration keys ()
+        {
+            faultContents ();
+            
+            return m_contents.keys ();
+        }
+
+
+        /**
+         * Creates a lazy property lookup based on 'src' contents.
+         * 
+         * @param src [null will result in empty property set created]
+         */
+        FilePropertyLookup (final File src)
+        {
+            m_src = src;
+        }
+        
+        /*
+         * @throws RuntimeException on file I/O failures. 
+         */
+        private synchronized void faultContents ()
+        {
+            Properties contents = m_contents;
+            if ((contents == null) && (m_src != null))
+            {
+                try
+                {
+                    contents = getPropertiesFromFile (m_src);
+                }
+                catch (RuntimeException re)
+                {
+                    throw re; // re-throw;
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException ("exception while processing properties file [" + m_src.getAbsolutePath () + "]: " + e);
+                }
+            }
+            
+            if (contents == null)
+            {
+                contents = new XProperties (); // non-null marker
+            }
+            
+            m_contents = contents;
+        }
+        
+
+        private final File m_src; // can be null
+        private Properties m_contents; // non-null after faultContents()
+        
+    } // end of nested class
+    
+
+    private static final class SystemPropertyLookup extends XProperties
+    {
+        // note: due to incredibly stupid coding in java.util.Properties
+        // (getProperty() uses a non-virtual call to get(), while propertyNames()
+        // uses a virtual call to the same instead of delegating to getProperty())
+        // I must override both methods below
+        
+        public String getProperty (final String key)
+        {
+            return (String) get (key);
+        }
+            
+        public Object get (final Object key)
+        {
+            if (! (key instanceof String)) return null;
+            
+            String result = (String) super.get (key);
+            if (result != null) return result;
+            
+            if (m_systemPrefix != null)
+            {
+                result = getSystemProperty (m_systemPrefix.concat ((String) key), null);
+                
+                if (result != null) return result;
+            }
+            
+            return result;
+        }
+        
+        /*
+         * Overrides Properties.keys () [this is used for debug logging only]
+         */
+        public synchronized Enumeration keys ()
+        {
+            final Hashtable _propertyNames = new Hashtable ();
+            
+            if (m_systemPrefix != null)
+            {
+                try
+                {
+                    final int systemPrefixLength = m_systemPrefix.length ();
+                    
+                    for (Enumeration e = System.getProperties ().propertyNames ();
+                         e.hasMoreElements (); )
+                    {
+                        final String n = (String) e.nextElement ();
+                        
+                        if (n.startsWith (m_systemPrefix))
+                        {
+                            final String yn = n.substring (systemPrefixLength);
+                            
+                            _propertyNames.put (yn, yn);
+                        }
+                    } 
+                }
+                catch (SecurityException ignore)
+                {
+                    ignore.printStackTrace (System.out);
+                    
+                    // continue
+                }
+            }
+            
+            return _propertyNames.keys ();
+        }
+
+                
+        SystemPropertyLookup (String systemPrefix)
+        {
+            if ((systemPrefix != null) && ! systemPrefix.endsWith ("."))
+                systemPrefix = systemPrefix.concat (".");
+                
+            m_systemPrefix = systemPrefix;
+        }
+        
+        
+        private final String m_systemPrefix; // can be null [if not null, normalized to end with "."]
+        
+    } // end of nested class
+    
+    
+    private static final class SystemRedirectsLookup extends XProperties
+    {
+        // note: due to incredibly stupid coding in java.util.Properties
+        // (getProperty() uses a non-virtual call to get(), while propertyNames()
+        // uses a virtual call to the same instead of delegating to getProperty())
+        // I must override both methods below
+        
+        public String getProperty (final String key)
+        {
+            return (String) get (key);
+        }
+            
+        public Object get (final Object key)
+        {
+            if (! (key instanceof String)) return null;
+            
+            String result = (String) super.get (key);
+            if (result != null) return result;
+            
+            if (m_systemRedirects != null)
+            {
+                final String redirect = (String) m_systemRedirects.get (key);
+                
+                if (redirect != null)
+                {
+                    result = getSystemProperty (redirect, null);
+                    if (result != null) return result;
+                }
+            }
+            
+            return result;
+        }
+        
+        /*
+         * Overrides Properties.keys () [this is used for debug logging only]
+         */
+        public synchronized Enumeration keys ()
+        {
+            final Hashtable _propertyNames = new Hashtable ();
+            
+            if (m_systemRedirects != null)
+            {
+                for (Iterator i = m_systemRedirects.keySet ().iterator ();
+                     i.hasNext (); )
+                {
+                    final Object key = i.next ();                    
+                    if (key != null) _propertyNames.put (key , key);
+                }
+            }
+            
+            return _propertyNames.keys ();
+        }
+
+                
+        SystemRedirectsLookup (final Map systemRedirects)
+        {
+            m_systemRedirects = systemRedirects; // note: no defensive copy
+        }
+        
+        
+        private final Map m_systemRedirects; // can be null
+        
+    } // end of nested class
+    
+    
+    private static String s_systemFingerprint;
+    private static Properties s_systemProperties, s_systemRedirects;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/ResourceLoader.java b/core/java12/com/vladium/util/ResourceLoader.java
new file mode 100644
index 0000000..13a53c1
--- /dev/null
+++ b/core/java12/com/vladium/util/ResourceLoader.java
@@ -0,0 +1,127 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ResourceLoader.java,v 1.1.1.1.2.1 2004/06/20 18:24:05 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+// ----------------------------------------------------------------------------
+/**
+ * A static API that can be used as a drop-in replacement for
+ * java.lang.ClassLoader API (the class/resource loading part). This
+ * implementation is merely a wrapper around ClassLoaderResolverget.ClassLoader()
+ * method.
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class ResourceLoader
+{
+    // public: ................................................................
+    
+    /**
+     * @see java.lang.ClassLoader#loadClass(java.lang.String)
+     */
+    public static Class loadClass (final String name)
+        throws ClassNotFoundException
+    {
+        final Class caller = ClassLoaderResolver.getCallerClass (1);
+        final ClassLoader loader = ClassLoaderResolver.getClassLoader (caller);
+        
+        return Class.forName (name, false, loader);
+    }
+
+    /**
+     * @see java.lang.ClassLoader#getResource(java.lang.String)
+     */    
+    public static URL getResource (final String name)
+    {
+        final Class caller = ClassLoaderResolver.getCallerClass (1);
+        final ClassLoader loader = ClassLoaderResolver.getClassLoader (caller);
+        
+        if (loader != null)
+            return loader.getResource (name);
+        else
+            return ClassLoader.getSystemResource (name);
+    }
+
+    /**
+     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
+     */        
+    public static InputStream getResourceAsStream (final String name)
+    {
+        final Class caller = ClassLoaderResolver.getCallerClass (1);
+        final ClassLoader loader = ClassLoaderResolver.getClassLoader (caller);
+        
+        if (loader != null)
+            return loader.getResourceAsStream (name);
+        else
+            return ClassLoader.getSystemResourceAsStream (name);
+    }
+
+    /**
+     * @see java.lang.ClassLoader#getResources(java.lang.String)
+     */            
+    public static Enumeration getResources (final String name)
+        throws IOException
+    {
+        final Class caller = ClassLoaderResolver.getCallerClass (1);
+        final ClassLoader loader = ClassLoaderResolver.getClassLoader (caller);
+        
+        if (loader != null)
+            return loader.getResources (name);
+        else
+            return ClassLoader.getSystemResources (name);
+    }
+    
+    
+    public static Class loadClass (final String name, final ClassLoader loader)
+        throws ClassNotFoundException
+    {
+        return Class.forName (name, false, loader != null ? loader : ClassLoader.getSystemClassLoader ());
+    }
+
+    public static URL getResource (final String name, final ClassLoader loader)
+    {
+        if (loader != null)
+            return loader.getResource (name);
+        else
+            return ClassLoader.getSystemResource (name);
+    }
+
+    public static InputStream getResourceAsStream (final String name, final ClassLoader loader)
+    {
+        if (loader != null)
+            return loader.getResourceAsStream (name);
+        else
+            return ClassLoader.getSystemResourceAsStream (name);
+    }
+
+    public static Enumeration getResources (final String name, final ClassLoader loader)
+        throws IOException
+    {
+        if (loader != null)
+            return loader.getResources (name);
+        else
+            return ClassLoader.getSystemResources (name);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private ResourceLoader () {} // prevent subclassing
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/SoftValueMap.java b/core/java12/com/vladium/util/SoftValueMap.java
new file mode 100644
index 0000000..3eefaaa
--- /dev/null
+++ b/core/java12/com/vladium/util/SoftValueMap.java
@@ -0,0 +1,619 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: SoftValueMap.java,v 1.1.1.1 2004/05/09 16:57:55 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+// ----------------------------------------------------------------------------
+/**
+ * MT-safety: an instance of this class is <I>not</I> safe for access from
+ * multiple concurrent threads [even if access is done by a single thread at a
+ * time]. The caller is expected to synchronize externally on an instance [the
+ * implementation does not do internal synchronization for the sake of efficiency].
+ * java.util.ConcurrentModificationException is not supported either.
+ *
+ * @author (C) 2002, Vlad Roubtsov
+ */
+public
+final class SoftValueMap implements Map
+{
+    // public: ................................................................
+
+    // TODO: for caching, does clearing of entries make sense? only to save
+    // entry memory -- which does not make sense if the set of key values is not
+    // growing over time... on the other hand, if the key set is unbounded,
+    // entry clearing is needed so that the hash table does not get polluted with
+    // empty-valued entries 
+    // TODO: provide mode that disables entry clearing 
+    // TODO: add shrinking rehashes (is it worth it?)
+
+    /**
+     * Equivalent to <CODE>SoftValueMap(1, 1)</CODE>.
+     */
+    public SoftValueMap ()
+    {
+        this (1, 1);
+    }
+    
+    /**
+     * Equivalent to <CODE>SoftValueMap(11, 0.75F, getClearCheckFrequency, putClearCheckFrequency)</CODE>.
+     */
+    public SoftValueMap (final int readClearCheckFrequency, final int writeClearCheckFrequency)
+    {
+        this (11, 0.75F, readClearCheckFrequency, writeClearCheckFrequency);
+    }
+    
+    /**
+     * Constructs a SoftValueMap with specified initial capacity, load factor,
+     * and cleared value removal frequencies.
+     *
+     * @param initialCapacity initial number of hash buckets in the table
+     * [may not be negative, 0 is equivalent to 1].
+     * @param loadFactor the load factor to use to determine rehashing points
+     * [must be in (0.0, 1.0] range].
+     * @param readClearCheckFrequency specifies that every readClearCheckFrequency
+     * {@link #get} should check for and remove all mappings whose soft values
+     * have been cleared by the garbage collector [may not be less than 1].
+     * @param writeClearCheckFrequency specifies that every writeClearCheckFrequency
+     * {@link #put} should check for and remove all mappings whose soft values
+     * have been cleared by the garbage collector [may not be less than 1].
+     */
+    public SoftValueMap (int initialCapacity, final float loadFactor, final int readClearCheckFrequency, final int writeClearCheckFrequency)
+    {
+        if (initialCapacity < 0)
+            throw new IllegalArgumentException ("negative input: initialCapacity [" + initialCapacity + "]");
+        if ((loadFactor <= 0.0) || (loadFactor >= 1.0 + 1.0E-6))
+            throw new IllegalArgumentException ("loadFactor not in (0.0, 1.0] range: " + loadFactor);
+        if (readClearCheckFrequency < 1)
+            throw new IllegalArgumentException ("readClearCheckFrequency not in [1, +inf) range: " + readClearCheckFrequency);
+        if (writeClearCheckFrequency < 1)
+            throw new IllegalArgumentException ("writeClearCheckFrequency not in [1, +inf) range: " + writeClearCheckFrequency);
+        
+        if (initialCapacity == 0) initialCapacity = 1;
+        
+        m_valueReferenceQueue = new ReferenceQueue ();
+        
+        m_loadFactor = loadFactor;
+        m_sizeThreshold = (int) (initialCapacity * loadFactor);
+        
+        m_readClearCheckFrequency = readClearCheckFrequency;
+        m_writeClearCheckFrequency = writeClearCheckFrequency;
+        
+        m_buckets = new SoftEntry [initialCapacity];
+    }
+    
+    
+    // unsupported operations:
+        
+    public boolean equals (final Object rhs)
+    {
+        throw new UnsupportedOperationException ("not implemented: equals");
+    }
+    
+    public int hashCode ()
+    {
+        throw new UnsupportedOperationException ("not implemented: hashCode");
+    }
+    
+    
+    /**
+     * Overrides Object.toString() for debug purposes.
+     */
+    public String toString ()
+    {
+        final StringBuffer s = new StringBuffer ();
+        debugDump (s);
+        
+        return s.toString ();
+    }
+    
+    
+    /**
+     * Returns the number of key-value mappings in this map. Some of the values
+     * may have been cleared already but not removed from the table.<P>
+     *
+     * <B>NOTE:</B> in contrast with the java.util.WeakHashMap implementation,
+     * this is a constant time operation.
+     */
+    public int size ()
+    {
+        return m_size;
+    }
+    
+    /**
+     * Returns 'false' is this map contains key-value mappings (even if some of
+     * the values may have been cleared already but not removed from the table).<P>
+     *
+     * <B>NOTE:</B> in contrast with the java.util.WeakHashMap implementation,
+     * this is a constant time operation.
+     */
+    public boolean isEmpty ()
+    {
+        return m_size == 0;
+    }
+    
+    /**
+     * Returns the value that is mapped to a given 'key'. Returns
+     * null if (a) this key has never been mapped or (b) a previously mapped
+     * value has been cleared by the garbage collector and removed from the table.
+     *
+     * @param key mapping key [may not be null].
+     *
+     * @return Object value mapping for 'key' [can be null].
+     */
+    public Object get (final Object key)
+    {
+        if (key == null) throw new IllegalArgumentException ("null input: key");
+        
+        if ((++ m_readAccessCount % m_readClearCheckFrequency) == 0) removeClearedValues ();
+        
+        // index into the corresponding hash bucket:
+        final int keyHashCode = key.hashCode ();
+        final SoftEntry [] buckets = m_buckets;
+        final int bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
+        
+        Object result = null; 
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (SoftEntry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            final Object entryKey = entry.m_key;
+
+            if (IDENTITY_OPTIMIZATION)
+            {
+                // note: this uses an early identity comparison opimization, making this a bit
+                // faster for table keys that do not override equals() [Thread, etc]
+                if ((key == entryKey) || ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey)))
+                {
+                    final Reference ref = entry.m_softValue;
+                    result = ref.get (); // may return null to the caller
+                    
+                    // [see comment for ENQUEUE_FOUND_CLEARED_ENTRIES]
+                    if (ENQUEUE_FOUND_CLEARED_ENTRIES && (result == null))
+                    {
+                        ref.enqueue ();
+                    }
+                    
+                    return result;
+                }
+            }
+            else
+            {
+                if ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey))
+                {
+                    final Reference ref = entry.m_softValue;
+                    result = ref.get (); // may return null to the caller
+                    
+                    // [see comment for ENQUEUE_FOUND_CLEARED_ENTRIES]
+                    if (ENQUEUE_FOUND_CLEARED_ENTRIES && (result == null))
+                    {
+                        ref.enqueue ();
+                    }
+                    
+                    return result;
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Updates the table to map 'key' to 'value'. Any existing mapping is overwritten.
+     *
+     * @param key mapping key [may not be null].
+     * @param value mapping value [may not be null].
+     *
+     * @return Object previous value mapping for 'key' [null if no previous mapping
+     * existed or its value has been cleared by the garbage collector and removed from the table].
+     */
+    public Object put (final Object key, final Object value)
+    {
+        if (key == null) throw new IllegalArgumentException ("null input: key");
+        if (value == null) throw new IllegalArgumentException ("null input: value");
+        
+        if ((++ m_writeAccessCount % m_writeClearCheckFrequency) == 0) removeClearedValues ();
+            
+        SoftEntry currentKeyEntry = null;
+        
+        // detect if 'key' is already in the table [in which case, set 'currentKeyEntry' to point to its entry]:
+        
+        // index into the corresponding hash bucket:
+        final int keyHashCode = key.hashCode ();
+        SoftEntry [] buckets = m_buckets;
+        int bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
+        
+        // traverse the singly-linked list of entries in the bucket:
+        for (SoftEntry entry = buckets [bucketIndex]; entry != null; entry = entry.m_next)
+        {
+            final Object entryKey = entry.m_key;
+            
+            if (IDENTITY_OPTIMIZATION)
+            {
+                // note: this uses an early identity comparison opimization, making this a bit
+                // faster for table keys that do not override equals() [Thread, etc]
+                if ((key == entryKey) || ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey)))
+                {
+                    currentKeyEntry = entry;
+                    break;
+                }
+            }
+            else
+            {
+                if ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey))
+                {
+                    currentKeyEntry = entry;
+                    break;
+                }
+            }
+        }
+        
+        if (currentKeyEntry != null)
+        {
+            // replace the current value:
+            
+            final IndexedSoftReference ref = currentKeyEntry.m_softValue;
+            final Object currentKeyValue = ref.get (); // can be null already [no need to work around the get() bug, though]
+            
+            if (currentKeyValue == null) ref.m_bucketIndex = -1; // disable removal by removeClearedValues() [need to do this because of the identity comparison there]
+            currentKeyEntry.m_softValue = new IndexedSoftReference (value, m_valueReferenceQueue, bucketIndex);
+            
+            return currentKeyValue; // may return null to the caller
+        }
+        else
+        {
+            // add a new entry:
+            
+            if (m_size >= m_sizeThreshold) rehash ();
+            
+            // recompute the hash bucket index:
+            buckets = m_buckets;
+            bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
+            final SoftEntry bucketListHead = buckets [bucketIndex];
+            final SoftEntry newEntry = new SoftEntry (m_valueReferenceQueue, key, value, bucketListHead, bucketIndex);
+            buckets [bucketIndex] = newEntry;
+            
+            ++ m_size;
+            
+            return null;
+        }
+    }
+    
+    public Object remove (final Object key)
+    {
+        if (key == null) throw new IllegalArgumentException ("null input: key");
+        
+        if ((++ m_writeAccessCount % m_writeClearCheckFrequency) == 0) removeClearedValues ();
+
+        // index into the corresponding hash bucket:
+        final int keyHashCode = key.hashCode ();
+        final SoftEntry [] buckets = m_buckets;
+        final int bucketIndex = (keyHashCode & 0x7FFFFFFF) % buckets.length;
+        
+        Object result = null;
+
+        // traverse the singly-linked list of entries in the bucket:
+        for (SoftEntry entry = buckets [bucketIndex], prev = null; entry != null; prev = entry, entry = entry.m_next)
+        {
+            final Object entryKey = entry.m_key;
+            
+            if ((IDENTITY_OPTIMIZATION && (entryKey == key)) || ((keyHashCode == entryKey.hashCode ()) && key.equals (entryKey)))
+            {
+                if (prev == null) // head of the list
+                {
+                    buckets [bucketIndex] = entry.m_next;
+                }
+                else
+                {
+                    prev.m_next = entry.m_next;
+                }
+                
+                final IndexedSoftReference ref = entry.m_softValue; 
+                result = ref.get (); // can be null already [however, no need to work around 4485942]
+                
+                // [regardless of whether the value has been enqueued or not, disable its processing by removeClearedValues() since the entire entry is removed here]
+                ref.m_bucketIndex = -1;
+                
+                // help GC:
+                entry.m_softValue = null;
+                entry.m_key = null;
+                entry.m_next = null;
+                entry = null;
+            
+                -- m_size;
+                break;
+            }
+        }
+        
+        return result;
+    }
+
+    
+    public void clear ()
+    {
+        final SoftEntry [] buckets = m_buckets;
+        
+        for (int b = 0, bLimit = buckets.length; b < bLimit; ++ b)
+        {
+            for (SoftEntry entry = buckets [b]; entry != null; )
+            {
+                final SoftEntry next = entry.m_next; // remember next pointer because we are going to reuse this entry
+
+                // [regardless of whether the value has been enqueued or not, disable its processing by removeClearedValues()]
+                entry.m_softValue.m_bucketIndex = -1;
+                
+                // help GC:
+                entry.m_softValue = null;
+                entry.m_next = null;
+                entry.m_key = null;
+                
+                entry = next;
+            }
+            
+            buckets [b] = null;
+        }
+        
+        m_size = 0;
+        m_readAccessCount = 0;
+        m_writeAccessCount = 0;
+    }
+
+
+    // unsupported operations:
+    
+    public boolean containsKey (final Object key)
+    {
+        throw new UnsupportedOperationException ("not implemented: containsKey");
+    }
+    
+    public boolean containsValue (final Object value)
+    {
+        throw new UnsupportedOperationException ("not implemented: containsValue");
+    }
+        
+    public void putAll (final Map map)
+    {
+        throw new UnsupportedOperationException ("not implemented: putAll");
+    }
+    
+    public Set keySet ()
+    {
+        throw new UnsupportedOperationException ("not implemented: keySet");
+    }
+    
+    public Set entrySet ()
+    {
+        throw new UnsupportedOperationException ("not implemented: entrySet");
+    }
+
+    public Collection values ()
+    {
+        throw new UnsupportedOperationException ("not implemented: values");
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    
+    void debugDump (final StringBuffer out)
+    {
+        if (out != null)
+        {
+            out.append (getClass ().getName ().concat ("@").concat (Integer.toHexString (System.identityHashCode (this)))); out.append (EOL);
+            out.append ("size = " + m_size + ", bucket table size = " + m_buckets.length + ", load factor = " + m_loadFactor + EOL);
+            out.append ("size threshold = " + m_sizeThreshold + ", get clear frequency = " + m_readClearCheckFrequency + ", put clear frequency = " + m_writeClearCheckFrequency + EOL);
+            out.append ("get count: " + m_readAccessCount + ", put count: " + m_writeAccessCount + EOL);
+        }
+    }
+
+    // private: ...............................................................
+
+
+    /**
+     * An extension of WeakReference that can store an index of the bucket it
+     * is associated with.
+     */
+    static class IndexedSoftReference extends SoftReference
+    {
+        IndexedSoftReference (final Object referent, ReferenceQueue queue, final int bucketIndex)
+        {
+            super (referent, queue);
+            
+            m_bucketIndex = bucketIndex;
+        }
+        
+        int m_bucketIndex;
+        
+    } // end of nested class
+    
+    
+    /**
+     * The structure used for chaining colliding keys.
+     */
+    static class SoftEntry
+    {
+        SoftEntry (final ReferenceQueue valueReferenceQueue, final Object key, Object value, final SoftEntry next, final int bucketIndex)
+        {
+            m_key = key;
+            
+            m_softValue = new IndexedSoftReference (value, valueReferenceQueue, bucketIndex); // ... do not retain a strong reference to the value
+            value = null;
+            
+            m_next = next;
+        }
+        
+        IndexedSoftReference m_softValue; // soft reference to the value [never null]
+        Object m_key;  // strong reference to the key [never null]
+        
+        SoftEntry m_next; // singly-linked list link
+        
+    } // end of nested class
+    
+
+    /**
+     * Re-hashes the table into a new array of buckets. During the process
+     * cleared value entries are discarded, making for another efficient cleared
+     * value removal method.
+     */
+    private void rehash ()
+    {
+        // TODO: it is possible to run this method twice, first time using the 2*k+1 prime sequencer for newBucketCount
+        // and then with that value reduced to actually shrink capacity. As it is right now, the bucket table can
+        // only grow in size
+        
+        final SoftEntry [] buckets = m_buckets;
+        
+        final int newBucketCount = (m_buckets.length << 1) + 1;
+        final SoftEntry [] newBuckets = new SoftEntry [newBucketCount];
+        
+        int newSize = 0;
+        
+        // rehash all entry chains in every bucket:
+        for (int b = 0, bLimit = buckets.length; b < bLimit; ++ b)
+        {
+            for (SoftEntry entry = buckets [b]; entry != null; )
+            {
+                final SoftEntry next = entry.m_next; // remember next pointer because we are going to reuse this entry
+                
+                IndexedSoftReference ref = entry.m_softValue; // get the soft value reference
+                                
+                Object entryValue = ref.get (); // convert the soft reference to a local strong one
+            
+                // skip entries whose keys have been cleared: this also saves on future removeClearedValues() work
+                if (entryValue != null)
+                {
+                    // [assertion: 'softValue' couldn't have been enqueued already and can't be enqueued until strong reference in 'entryKey' is nulled out]
+                    
+                    // index into the corresponding new hash bucket:
+                    final int entryKeyHashCode = entry.m_key.hashCode ();
+                    final int newBucketIndex = (entryKeyHashCode & 0x7FFFFFFF) % newBucketCount;
+                    
+                    final SoftEntry bucketListHead = newBuckets [newBucketIndex];
+                    entry.m_next = bucketListHead;
+                    newBuckets [newBucketIndex] = entry;
+                    
+                    // adjust bucket index:
+                    ref.m_bucketIndex = newBucketIndex;
+            
+                    ++ newSize;
+                    
+                    entryValue = null;
+                }
+                else
+                {
+                    // ['softValue' may or may not have been enqueued already]
+                    
+                    // adjust bucket index:
+                    // [regardless of whether 'softValue' has been enqueued or not, disable its removal by removeClearedValues() since the buckets get restructured]
+                    ref.m_bucketIndex = -1;
+                }
+                
+                entry = next;
+            }
+        }
+        
+        if (DEBUG)
+        {
+            if (m_size > newSize) System.out.println ("DEBUG: rehash() cleared " + (m_size - newSize) + " values, new size = " + newSize);
+        }
+        
+        m_size = newSize;
+        m_sizeThreshold = (int) (newBucketCount * m_loadFactor);
+        m_buckets = newBuckets;
+    }
+    
+
+    /**
+     * Removes all entries whose soft values have been cleared _and_ enqueued.
+     * See comments below for why this is safe wrt to rehash().
+     */
+    private void removeClearedValues ()
+    {
+        int count = 0;
+        
+next:   for (Reference _ref; (_ref = m_valueReferenceQueue.poll ()) != null; )
+        {
+            // remove entry containing '_ref' using its bucket index and identity comparison:
+            
+            // index into the corresponding hash bucket:
+            final int bucketIndex = ((IndexedSoftReference) _ref).m_bucketIndex;
+            
+            if (bucketIndex >= 0) // skip keys that were already removed by rehash()
+            {
+                // [assertion: this reference was not cleared when the last rehash() ran and so its m_bucketIndex is correct]
+                
+                // traverse the singly-linked list of entries in the bucket:
+                for (SoftEntry entry = m_buckets [bucketIndex], prev = null; entry != null; prev = entry, entry = entry.m_next)
+                {
+                    if (entry.m_softValue == _ref)
+                    {
+                        if (prev == null) // head of the list
+                        {
+                            m_buckets [bucketIndex] = entry.m_next;
+                        }
+                        else
+                        {
+                            prev.m_next = entry.m_next;
+                        }
+                    
+                        entry.m_softValue = null;
+                        entry.m_key = null;
+                        entry.m_next = null;
+                        entry = null;
+                    
+                        -- m_size;
+                        
+                        if (DEBUG) ++ count;
+                        continue next;
+                    }
+                }
+                
+                // no match found this can happen if a soft value got replaced by a put
+                
+                final StringBuffer msg = new StringBuffer ("removeClearedValues(): soft reference [" + _ref + "] did not match within bucket #" + bucketIndex + EOL);
+                debugDump (msg);
+            
+                throw new Error (msg.toString ());
+            }
+            // else: it has already been removed by rehash() or other methods
+        }
+        
+        if (DEBUG)
+        {
+            if (count > 0) System.out.println ("DEBUG: removeClearedValues() cleared " + count + " keys, new size = " + m_size);
+        }
+    }
+    
+    
+    private final ReferenceQueue m_valueReferenceQueue; // reference queue for all references used by SoftEntry objects used by this table
+    private final float m_loadFactor; // determines the setting of m_sizeThreshold
+    private final int m_readClearCheckFrequency, m_writeClearCheckFrequency; // parameters determining frequency of running removeClearedKeys() by get() and put()/remove(), respectively
+    
+    private SoftEntry [] m_buckets; // table of buckets
+    private int m_size; // number of values in the table, not cleared as of last check
+    private int m_sizeThreshold; // size threshold for rehashing
+    private int m_readAccessCount, m_writeAccessCount;
+    
+    private static final String EOL = System.getProperty ("line.separator", "\n");
+    
+    private static final boolean IDENTITY_OPTIMIZATION          = true;
+    
+    // setting this to 'true' is an optimization and a workaround for bug 4485942:
+    private static final boolean ENQUEUE_FOUND_CLEARED_ENTRIES  = true; 
+    
+    private static final boolean DEBUG = false;
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/util/Strings.java b/core/java12/com/vladium/util/Strings.java
new file mode 100644
index 0000000..446812e
--- /dev/null
+++ b/core/java12/com/vladium/util/Strings.java
@@ -0,0 +1,299 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Strings.java,v 1.1.1.1 2004/05/09 16:57:55 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class Strings
+{
+    // public: ................................................................
+    
+    
+    public static final String WHITE_SPACE = " \t\r\n";
+    
+    
+    //TODO: add duplicate removal
+    public static String toListForm (final String [] strings, final char delimiter)
+    {
+        if (strings == null) return null;
+        if (strings.length == 0) return "";
+        
+        final StringBuffer s = new StringBuffer ();
+        for (int i = 0, iLimit = strings.length; i < iLimit; ++ i)
+        {
+            if (i != 0) s.append (delimiter);
+            s.append (strings [i]);
+        }
+        
+        return s.toString ();
+    }
+    
+    public static String [] removeDuplicates (final String [] strings, final boolean removeNull)
+    {
+        if (strings == null) return strings;
+        
+        final int length = strings.length;
+        if (length == 0) return strings;
+        
+        final Set /* String */ _strings = new HashSet (length);
+        final List /* String */ _result = new ArrayList (length);
+        
+        for (int i = 0; i < length; ++ i)
+        {
+            final String s = strings [i];
+            if (removeNull && (s == null)) continue;
+            
+            if (_strings.add (s)) _result.add (s);
+        }
+        
+        final int resultLength = _result.size (); 
+        if (resultLength == length)
+            return strings;
+        else
+        {
+            final String [] result = new String [resultLength];
+            _result.toArray (result);
+            
+            return result; 
+        }
+    }
+    
+    /**
+     * Also removes duplicates.
+     * 
+     * @param strings
+     * @param delimiters
+     * @param removeNull
+     * @return
+     */
+    public static String [] merge (final String [] strings, final String delimiters, final boolean removeNull)
+    {
+        if (strings == null) return strings;
+        
+        final int length = strings.length;
+        if (length == 0) return strings;
+        
+        if ((delimiters == null) || (delimiters.length () == 0))
+            throw new IllegalArgumentException ("null/empty input: delimiters");
+        
+        final Set /* String */ _strings = new HashSet (length);
+        final List /* String */ _result = new ArrayList (length);
+        
+        for (int i = 0; i < length; ++ i)
+        {
+            final String s = strings [i];
+            if (removeNull && (s == null)) continue;
+            
+            final StringTokenizer tokenizer = new StringTokenizer (s, delimiters);
+            while (tokenizer.hasMoreTokens ())
+            {
+                final String ss = tokenizer.nextToken ();
+                if (_strings.add (ss)) _result.add (ss);
+            }
+        }
+        
+        final String [] result = new String [_result.size ()];
+        _result.toArray (result);
+            
+        return result; 
+    }
+    
+    /**
+     * Removes duplicates.
+     * 
+     * @param delimiters
+     * @param processAtFiles
+     * @return
+     * @throws IOException
+     */
+    public static String [] mergeAT (final String [] strings, final String delimiters, final boolean processAtFiles)
+        throws IOException
+    {
+        if (! processAtFiles)
+            return merge (strings, delimiters, true);
+        else
+        {
+            if (strings == null) return strings;
+        
+            final int length = strings.length;
+            if (length == 0) return strings;
+            
+            if ((delimiters == null) || (delimiters.length () == 0))
+                throw new IllegalArgumentException ("null/empty input: delimiters");
+            
+            final Set /* String */ _strings = new HashSet (length);
+            final List /* String */ _result = new ArrayList (length);
+            
+            for (int i = 0; i < length; ++ i)
+            {
+                final String s = strings [i];
+                if (s == null) continue;
+                
+                final StringTokenizer tokenizer = new StringTokenizer (s, delimiters);
+                while (tokenizer.hasMoreTokens ())
+                {
+                    final String ss = tokenizer.nextToken ();
+                        
+                    if (ss.startsWith ("@"))
+                    {
+                        final String [] fileList = Files.readFileList (new File (ss.substring (1)));
+                        for (int j = 0; j < fileList.length; ++ j)
+                        {
+                            final String sss = fileList [j];
+                            if (_strings.add (sss)) _result.add (sss);
+                        }
+                    }
+                    else if (_strings.add (ss)) _result.add (ss);
+                }
+            }
+            
+            final String [] result = new String [_result.size ()];
+            _result.toArray (result);
+                
+            return result;
+        }
+    }
+    
+    /**
+     * HTML attribute values can be quoted using either double or single quotes.
+     * Depending on the type of quote used, the other kind can be used unescaped
+     * within the attribute value. This method assumes that only double quotes
+     * are used for delimiting, hence this is the only kind that is escaped.
+     */
+    public static void HTMLEscape (final String s, final StringBuffer append)
+    {
+        if (s == null) throw new IllegalArgumentException ("null input: s");
+        if (append == null) throw new IllegalArgumentException ("null input: append");
+        
+        final char [] chars;
+        if (USE_GET_CHARS) chars = s.toCharArray (); 
+        
+        for (int i = 0, iLimit = s.length (); i < iLimit; ++ i)
+        {
+            final char c = USE_GET_CHARS ? chars [i] : s.charAt (i);
+            
+            switch (c)
+            {
+                case '<':
+                    append.append ("&lt;");
+                    break;
+                    
+                case '>':
+                    append.append ("&gt;");
+                    break;
+
+                case '"':
+                    append.append ("&#34;");
+                    break;
+                    
+                case '&':
+                    append.append ("&amp;");
+                    break;
+                
+                default:
+                    append.append (c);    
+                
+            } // end of switch
+        }
+    }
+    
+    /**
+     * Same as {@link #HTMLEscape(String, StringBuffer)} but also replaces spaces
+     * with "&nbsp;"'s, which is handy for escaping code. 
+     */
+    public static void HTMLEscapeNB (final String s, final StringBuffer append)
+    {
+        if (s == null) throw new IllegalArgumentException ("null input: s");
+        if (append == null) throw new IllegalArgumentException ("null input: append");
+        
+        final char [] chars;
+        if (USE_GET_CHARS) chars = s.toCharArray (); 
+        
+        for (int i = 0, iLimit = s.length (); i < iLimit; ++ i)
+        {
+            final char c = USE_GET_CHARS ? chars [i] : s.charAt (i);
+            
+            switch (c)
+            {
+                case ' ':
+                    append.append ('\u00A0'); // don't use "&#160;": a waste of space
+                    break;
+                    
+                case '\t':
+                    append.append ("\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0"); // TODO: define a prop for this
+                    break;
+                    
+//                case '-':
+//                    append.append ((char) 0x8209);
+//                    break;
+                    
+                case '<':
+                    append.append ("&lt;");
+                    break;
+                    
+                case '>':
+                    append.append ("&gt;");
+                    break;
+
+                case '"':
+                    append.append ("&#34;");
+                    break;
+                    
+                case '&':
+                    append.append ("&amp;");
+                    break;
+                
+                default:
+                    append.append (c);    
+                
+            } // end of switch
+        }
+    }
+    
+    public static String HTMLEscape (final String s)
+    {
+        final StringBuffer buf = new StringBuffer ();
+        HTMLEscape (s, buf);
+        
+        return buf.toString ();
+    }
+    
+    public static String HTMLEscapeSP (final String s)
+    {
+        final StringBuffer buf = new StringBuffer ();
+        HTMLEscapeNB (s, buf);
+        
+        return buf.toString ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private Strings () {} // prevent subclassing
+    
+    
+    private static final boolean USE_GET_CHARS = true;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/WCMatcher.java b/core/java12/com/vladium/util/WCMatcher.java
new file mode 100644
index 0000000..2c4dfe5
--- /dev/null
+++ b/core/java12/com/vladium/util/WCMatcher.java
@@ -0,0 +1,368 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: WCMatcher.java,v 1.1.1.1 2004/05/09 16:57:56 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2002
+ */
+public
+abstract class WCMatcher
+{
+    // public: ................................................................
+    
+
+    public static WCMatcher compile (final String pattern)
+    {
+        if (pattern == null) throw new IllegalArgumentException ("null input: pattern");
+        
+        final char [] chars = pattern.toCharArray (); // is this faster than using charAt()?
+        final int charsLength = chars.length;
+        
+        if (charsLength == 0)
+            return EMPTY_MATCHER; // TODO: should be an EMPTY_MATCHER
+        else
+        {
+            int patternLength = 0, starCount = 0, questionCount = 0;
+            boolean star = false;
+            
+            for (int c = 0; c < charsLength; ++ c)
+            {
+                final char ch = chars [c];
+                if (ch == '*')
+                {
+                    if (! star)
+                    {
+                        star = true;
+                        ++ starCount; 
+                        chars [patternLength ++] = '*';
+                    }
+                }
+                else
+                {
+                    star = false;
+                    if (ch == '?') ++ questionCount;
+                    chars [patternLength ++] = ch;
+                }
+            }
+            
+            // [assertion: patternLength > 0]
+            
+            if ((starCount == 1) && (questionCount == 0))
+            {
+                if (patternLength == 1)
+                    return ALL_MATCHER;
+                else if (chars [0] == '*')
+                    return new EndsWithMatcher (chars, patternLength);
+                else if (chars [patternLength - 1] == '*')
+                    return new StartsWithMatcher (chars, patternLength);
+            }
+            
+            return new PatternMatcher (chars, patternLength);
+        }
+    }
+    
+    public abstract boolean matches (String s);
+    public abstract boolean matches (char [] chars);
+
+        
+//    private boolean matches (int pi, int si, final char [] string)
+//    {
+//        System.out.println ("pi = " + pi + ", si = " + si);
+//        
+//        if (pi == m_pattern.length)
+//            return si == string.length;
+//        else
+//        {
+//            switch (m_pattern [pi])
+//            {
+//                case '?':
+//                {
+//                    return (si < string.length) && matches (pi + 1, si + 1, string);
+//                }
+//                
+//                case '*':
+//                {
+//                    return matches (pi + 1, si, string) || ((si < string.length) && matches (pi, si + 1, string));
+//                }
+//                
+//                default:
+//                {
+//                    return (si < string.length) && (m_pattern [pi] == string [si]) && matches (pi + 1, si + 1, string);
+//                }
+//                
+//            } // end of switch
+//        }
+//    }
+    
+
+        
+    // protected: .............................................................
+    
+    // package: ...............................................................
+
+
+    WCMatcher () {}
+    
+    // private: ...............................................................
+    
+    
+    private static final class AllMatcher extends WCMatcher
+    {
+        public final boolean matches (final String s)
+        {
+            if (s == null) throw new IllegalArgumentException  ("null input: s");
+            
+            return true;
+        }
+        
+        public final boolean matches (final char [] chars)
+        {
+            if (chars == null) throw new IllegalArgumentException  ("null input: chars");
+            
+            return true;
+        }
+        
+    } // end of nested class
+    
+
+    private static final class EmptyMatcher extends WCMatcher
+    {
+        public final boolean matches (final String s)
+        {
+            if (s == null) throw new IllegalArgumentException  ("null input: s");
+            
+            return false;
+        }
+        
+        public final boolean matches (final char [] chars)
+        {
+            if (chars == null) throw new IllegalArgumentException  ("null input: chars");
+            
+            return chars.length == 0;
+        }
+        
+    } // end of nested class
+    
+    
+    private static final class StartsWithMatcher extends WCMatcher
+    {
+        public final boolean matches (final String s)
+        {
+            if (s == null) throw new IllegalArgumentException  ("null input: s");
+            
+            return s.startsWith (m_prefix);
+        }
+        
+        public final boolean matches (final char [] chars)
+        {
+            if (chars == null) throw new IllegalArgumentException  ("null input: chars");
+            
+            final char [] prefixChars = m_prefixChars;
+            final int prefixLength = prefixChars.length - 1;
+            
+            if (chars.length < prefixLength) return false;
+            
+            for (int c = 0; c < prefixLength; ++ c)
+            {
+                if (chars [c] != prefixChars [c]) return false; 
+            }
+            
+            return true;
+        }
+        
+        StartsWithMatcher (final char [] pattern, final int patternLength)
+        {
+            m_prefixChars = pattern;            
+            m_prefix = new String (pattern, 0, patternLength - 1);
+        }
+        
+        private final char [] m_prefixChars;
+        private final String m_prefix;
+        
+    } // end of nested class
+    
+    
+    private static final class EndsWithMatcher extends WCMatcher
+    {
+        public final boolean matches (final String s)
+        {
+            if (s == null) throw new IllegalArgumentException  ("null input: s");
+            
+            return s.endsWith (m_suffix);
+        }
+        
+        public final boolean matches (final char [] chars)
+        {
+            if (chars == null) throw new IllegalArgumentException  ("null input: chars");
+            
+            final char [] suffixChars = m_suffixChars;
+            final int suffixLength = suffixChars.length - 1;
+            final int charsLength = chars.length;
+            
+            if (charsLength < suffixLength) return false;
+            
+            for (int c = 0; c < suffixLength; ++ c)
+            {
+                if (chars [charsLength - 1 - c] != suffixChars [suffixLength - c]) return false; 
+            }
+            
+            return true;
+        }
+        
+        EndsWithMatcher (final char [] pattern, final int patternLength)
+        {
+            m_suffixChars = pattern;
+            m_suffix = new String (pattern, 1, patternLength - 1);
+        }
+        
+        private final char [] m_suffixChars;
+        private final String m_suffix;
+        
+    } // end of nested class
+    
+
+    private static final class PatternMatcher extends WCMatcher
+    {
+        public final boolean matches (final String s)
+        {
+            if (s == null) throw new IllegalArgumentException  ("null input: s");
+
+            final char [] string = s.toCharArray (); // implies an array copy; is this faster than using charAt()?
+            final int stringLength = string.length;
+
+            final char [] pattern = m_pattern;
+            final int patternLength = m_patternLength;
+            
+            // [assertion: patternLength > 0]
+            
+            int si = 0, pi = 0;
+            boolean star = false;
+            
+            
+      next: while (true)
+            {
+                //System.out.println ("pi = " + pi + ", si = " + si);
+                
+                int i = 0;
+                for ( ; pi + i < patternLength; ++ i)
+                {
+                    final char patternChar = pattern [pi + i];
+                     
+                    if (patternChar == '*')
+                    {
+                        si += i;
+                        pi += (i + 1);
+                        
+                        star = true;
+                        continue next;
+                    }
+                    
+                    final int si_i = si + i;
+                     
+                    if (si_i == stringLength) return false;
+                    
+                    if (patternChar != string [si_i])
+                    {
+                        if (patternChar == '?') continue;
+                        
+                        if (! star) return false;
+                        ++ si;
+                        
+                        continue next;
+                    }
+                    
+                } // end of for
+                
+                if (si + i == stringLength) return true;
+                
+                if (! star) return false;
+                ++ si;
+                
+                // [continue next;]
+            }
+        }
+        
+        
+        public final boolean matches (final char [] string)
+        {
+            if (string == null) throw new IllegalArgumentException  ("null input: string");
+
+            final int stringLength = string.length;
+
+            final char [] pattern = m_pattern;
+            final int patternLength = m_patternLength;
+            
+            // [assertion: patternLength > 0]
+            
+            int si = 0, pi = 0;
+            boolean star = false;
+            
+            
+      next: while (true)
+            {
+                //System.out.println ("pi = " + pi + ", si = " + si);
+                
+                int i = 0;
+                for ( ; pi + i < patternLength; ++ i)
+                {
+                    final char patternChar = pattern [pi + i];
+                     
+                    if (patternChar == '*')
+                    {
+                        si += i;
+                        pi += (i + 1);
+                        
+                        star = true;
+                        continue next;
+                    }
+                    
+                    final int si_i = si + i;
+                     
+                    if (si_i == stringLength) return false;
+                    
+                    if (patternChar != string [si_i])
+                    {
+                        if (patternChar == '?') continue;
+                        
+                        if (! star) return false;
+                        ++ si;
+                        
+                        continue next;
+                    }
+                    
+                } // end of for
+                
+                if (si + i == stringLength) return true;
+                
+                if (! star) return false;
+                ++ si;
+                
+                // [continue next;]
+            }
+        }
+        
+        PatternMatcher (final char [] pattern, final int patternLength)
+        {
+            m_pattern = pattern;
+            m_patternLength = patternLength;
+        }
+        
+        
+        private final char [] m_pattern;
+        private final int m_patternLength;
+        
+    } // end of nested class
+
+        
+    private static final WCMatcher ALL_MATCHER = new AllMatcher ();
+    private static final WCMatcher EMPTY_MATCHER = new EmptyMatcher ();
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/XProperties.java b/core/java12/com/vladium/util/XProperties.java
new file mode 100644
index 0000000..a09d82d
--- /dev/null
+++ b/core/java12/com/vladium/util/XProperties.java
@@ -0,0 +1,83 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: XProperties.java,v 1.1.1.1 2004/05/09 16:57:56 vlad_r Exp $
+ */
+package com.vladium.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+class XProperties extends Properties
+{
+    // public: ................................................................
+    
+    
+    public XProperties ()
+    {
+    }
+    
+    public XProperties (final Properties base)
+    {
+        super (base);
+    }
+    
+    public void list (final PrintStream out)
+    {
+        final Set /* String */ _propertyNames = new TreeSet ();
+        
+        // note: must use propertyNames() because that is the only method that recurses
+        for (Enumeration propertyNames = propertyNames (); propertyNames.hasMoreElements (); )
+        {
+            _propertyNames.add (propertyNames.nextElement ());
+        }
+        
+        for (Iterator i = _propertyNames.iterator (); i.hasNext (); )
+        {
+            final String n = (String) i.next ();
+            final String v = getProperty (n);
+            
+            out.println (n + ":\t[" + v + "]");
+        }
+    }
+    
+    public void list (final PrintWriter out)
+    {
+        final Set /* String */ _propertyNames = new TreeSet ();
+        
+        // note: must use propertyNames() because that is the only method that recurses
+        for (Enumeration propertyNames = propertyNames (); propertyNames.hasMoreElements (); )
+        {
+            _propertyNames.add (propertyNames.nextElement ());
+        }
+        
+        for (Iterator i = _propertyNames.iterator (); i.hasNext (); )
+        {
+            final String n = (String) i.next ();
+            final String v = getProperty (n);
+            
+            out.println (n + ":\t[" + v + "]");
+        }
+    }
+    
+    // protected: .............................................................
+    
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/args/IOptsParser.java b/core/java12/com/vladium/util/args/IOptsParser.java
new file mode 100644
index 0000000..38cbbea
--- /dev/null
+++ b/core/java12/com/vladium/util/args/IOptsParser.java
@@ -0,0 +1,78 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IOptsParser.java,v 1.1.1.1 2004/05/09 16:57:56 vlad_r Exp $
+ */
+package com.vladium.util.args;
+
+import java.io.PrintWriter;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2002
+ */
+public
+interface IOptsParser
+{
+    // public: ................................................................
+
+    int SHORT_USAGE     = 1;
+    int DETAILED_USAGE  = 2;
+
+    interface IOpt
+    {
+        String getName ();
+        String getCanonicalName ();
+        
+        String getPatternPrefix ();
+        
+        int getValueCount ();
+        String getFirstValue ();
+        String [] getValues ();
+      
+    } // end of interface
+    
+    
+    interface IOpts
+    {
+        /**
+         * 0: none, 1: short, 2: detailed
+         * 
+         * @return
+         */
+        int usageRequestLevel ();
+        void error (PrintWriter out, int width);
+        
+        IOpt [] getOpts ();
+        boolean hasArg (String name);
+        
+        IOpt [] getOpts (String pattern);
+        
+        /**
+         * 
+         * @return [never null, could be empty]
+         */
+        String [] getFreeArgs ();
+        
+    } // end of interface
+    
+    void usage (PrintWriter out, int level, int width);
+    IOpts parse (String [] args);
+    
+    abstract class Factory
+    {
+        // TODO: pass short/long usage opt names in?
+        
+        public static IOptsParser create (final String metadataResourceName, final ClassLoader loader,
+                                          final String msgPrefix, final String [] usageOpts)
+        {
+            return new OptsParser (metadataResourceName, loader, msgPrefix, usageOpts);
+        }
+        
+    } // end of nested class
+    
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/args/OptsParser.java b/core/java12/com/vladium/util/args/OptsParser.java
new file mode 100644
index 0000000..9030d66
--- /dev/null
+++ b/core/java12/com/vladium/util/args/OptsParser.java
@@ -0,0 +1,1598 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: OptsParser.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $
+ */
+package com.vladium.util.args;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.vladium.util.IConstants;
+import com.vladium.util.ResourceLoader;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2002
+ */
+final class OptsParser implements IOptsParser
+{
+    // public: ................................................................
+
+    // TODO: #-comments
+    // TODO: prefixing for error messages
+    // TODO: support var subst (main class name, etc)
+    // TODO: support short/full usage
+    // TODO: support marking opts as for displayable in full usage only
+    
+    public synchronized void usage (final PrintWriter out, final int level, final int width)
+    {
+        // TODO: use width
+        // TODO: cache?
+        
+        final String prefix = OPT_PREFIXES [CANONICAL_OPT_PREFIX];
+        
+        for (Iterator i = m_metadata.getOptDefs (); i.hasNext (); )
+        {
+            final OptDef optdef = (OptDef) i.next ();
+            
+            if ((level < 2) && optdef.isDetailedOnly ()) // skip detailed usage only options 
+                continue;
+            
+            final StringBuffer line = new StringBuffer ("  ");
+            
+            final String canonicalName = optdef.getCanonicalName ();
+            final boolean isPattern = optdef.isPattern (); 
+             
+            line.append (prefix);
+            line.append (canonicalName);
+            if (isPattern) line.append ('*');
+            
+            final String [] names = optdef.getNames ();
+            for (int n = 0; n < names.length; ++ n)
+            {
+                final String name = names [n];
+                if (! name.equals (canonicalName))
+                {
+                    line.append (", ");
+                    
+                    line.append (prefix);
+                    line.append (name);
+                    if (isPattern) line.append ('*');
+                }
+            }
+
+            final String vmnemonic = optdef.getValueMnemonic ();
+            if (vmnemonic != null)
+            {
+                line.append (' ');
+                line.append (vmnemonic);
+            }
+
+            
+            int padding = 16 - line.length ();
+            if (padding < 2)
+            {
+                // end the current line
+                out.println (line);
+                
+                line.setLength (0);
+                for (int p = 0; p < 16; ++ p) line.append (' ');
+            }
+            else
+            {          
+                for (int p = 0; p < padding; ++ p) line.append (' ');
+            }
+            
+            if (optdef.isRequired ()) line.append ("{required} ");
+            line.append (optdef.getDescription ());
+            
+            out.println (line);
+        }
+        
+        if (level < DETAILED_USAGE)
+        {
+            final OptDef usageOptDef = m_metadata.getUsageOptDef ();
+            if ((usageOptDef != null) && (usageOptDef.getNames () != null) && (usageOptDef.getNames ().length > 1))
+            {
+                out.println (); 
+                out.println ("  {use '" + usageOptDef.getNames () [1] + "' option to see detailed usage help}");
+            }
+        }
+    }
+    
+    public synchronized IOpts parse (final String [] args)
+    {
+        if (args == null) throw new IllegalArgumentException ("null input: args");
+        
+        final Opts opts = new Opts ();
+        
+        {
+            final String [] nv = new String [2]; // out buffer for getOptNameAndValue()
+            final String [] pp = new String [1]; // out buffer for getOptDef()
+            
+            // running state/current vars:
+            int state = STATE_OPT;
+            OptDef optdef = null;
+            Opt opt = null;
+            String value = null;
+            int valueCount = 0;
+            
+            int a;
+      scan: for (a = 0; a < args.length; )
+            {
+                final String av = args [a];
+                if (av == null) throw new IllegalArgumentException ("null input: args[" + a + "]");
+                
+                //System.out.println ("[state: " + state + "] av = " + av);
+                
+                switch (state)
+                {
+                    case STATE_OPT:
+                    {
+                        if (isOpt (av, valueCount, optdef))
+                        {
+                            // 'av' looks like an option: get its name and see if it
+                            // is in the metadata
+                            
+                            valueCount = 0;
+                           
+                            getOptNameAndValue (av, nv); // this can leave nv[1] as null
+                           
+                            // [assertion: nv [0] != null]
+                           
+                            final String optName = nv [0]; // is not necessarily canonical
+                            optdef = m_metadata.getOptDef (optName, pp); // pp [0] is always set by this
+                           
+                            if (optdef == null)
+                            {
+                                // unknown option:
+                               
+                                // TODO: coded messages?
+                                opts.addError (formatMessage ("unknown option \'" + optName + "\'"));
+                               
+                                state = STATE_ERROR;
+                            }
+                            else
+                            {
+                                // merge if necessary:
+                                
+                                final String canonicalName = getOptCanonicalName (optName, optdef);
+                                final String patternPrefix = pp [0];
+    
+                                opt = opts.getOpt (canonicalName);
+                                
+                                if (optdef.isMergeable ())
+                                {
+                                    if (opt == null)
+                                    {
+                                        opt = new Opt (optName, canonicalName, patternPrefix);
+                                        opts.addOpt (opt, optdef, optName);
+                                    }
+                                }
+                                else
+                                {
+                                    if (opt == null)
+                                    {
+                                        opt = new Opt (optName, canonicalName, patternPrefix);
+                                        opts.addOpt (opt, optdef, optName);
+                                    }
+                                    else
+                                    {
+                                        opts.addError (formatMessage ("option \'" + optName + "\' cannot be specified more than once"));
+                               
+                                        state = STATE_ERROR;
+                                    }
+                                }
+
+                                value = nv [1];
+                                
+                                if (value == null) ++ a;
+                                state = STATE_OPT_VALUE;
+                            }
+                        }
+                        else
+                        {
+                            // not in STATE_OPT_VALUE and 'av' does not look
+                            // like an option: the rest of args are free
+                           
+                            state = STATE_FREE_ARGS;
+                        }
+                    }
+                    break;
+                    
+                    
+                    case STATE_OPT_VALUE:
+                    {
+                        // [assertion: opt != null and optdef != null]
+                        
+                        if (value != null)
+                        {
+                            // value specified explicitly using the <name>separator<value> syntax:
+                            // [don't shift a]
+                            
+                            valueCount = 1;
+                             
+                            final int [] cardinality = optdef.getValueCardinality ();
+                            
+                            if (cardinality [1] < 1)
+                            {
+                                opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept values: \'" + value + "\'"));
+                               
+                                state = STATE_ERROR;
+                            }
+                            else
+                            {
+                                ++ a;
+                                opt.addValue (value);
+                            }
+                        }
+                        else
+                        {
+                            value = args [a];
+                            
+                            final int [] cardinality = optdef.getValueCardinality ();
+                        
+                            if (isOpt (value, valueCount, optdef))
+                            {
+                                if (valueCount < cardinality [0])
+                                {
+                                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
+                                   
+                                    state = STATE_ERROR;
+                                }
+                                else
+                                    state = STATE_OPT;
+                            }
+                            else
+                            {
+                                if (valueCount < cardinality [1])
+                                {
+                                    ++ valueCount;
+                                    ++ a;
+                                    opt.addValue (value);
+                                }
+                                else
+                                {
+                                    // this check is redundant:
+//                                    if (valueCount < cardinality [0])
+//                                    {
+//                                        opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
+//                                       
+//                                        state = STATE_ERROR;
+//                                    }
+//                                    else
+                                        state = STATE_FREE_ARGS;
+                                } 
+                            }
+                        }
+                        
+                        value = null;
+                    }
+                    break;
+                    
+                    
+                    case STATE_FREE_ARGS:
+                    {
+                        if (isOpt (args [a], valueCount, optdef))
+                        {
+                            state = STATE_OPT;
+                        }
+                        else
+                        {
+                            opts.setFreeArgs (args, a);
+                            break scan;
+                        }
+                    }
+                    break;
+                    
+                    
+                    case STATE_ERROR:
+                    {
+                        break scan; // TODO: could use the current value of 'a' for a better error message
+                    }
+                    
+                } // end of switch
+            }
+            
+            if (a == args.length)
+            {
+                if (opt != null) // validate the last option's min cardinality
+                {
+                    final int [] cardinality = optdef.getValueCardinality ();
+                    
+                    if (valueCount < cardinality [0])
+                    {
+                        opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
+                    }
+                }
+                else
+                {
+                    opts.setFreeArgs (args, a);
+                }
+            }
+            
+        } // end of 'args' parsing
+        
+        
+        final IOpt [] specified = opts.getOpts ();
+        if (specified != null)
+        {
+            // validation: all required parameters must be specified
+            
+            final Set /* String(canonical name) */ required = new HashSet ();
+            required.addAll (m_metadata.getRequiredOpts ());
+            
+            for (int s = 0; s < specified.length; ++ s)
+            {
+                required.remove (specified [s].getCanonicalName ());
+            }
+            
+            if (! required.isEmpty ())
+            {
+                for (Iterator i = required.iterator (); i.hasNext (); )
+                {
+                    opts.addError (formatMessage ("missing required option \'" + (String) i.next () + "\'"));
+                }
+            }
+            
+            for (int s = 0; s < specified.length; ++ s)
+            {
+                final IOpt opt = specified [s];
+                final OptDef optdef = m_metadata.getOptDef (opt.getCanonicalName (), null); 
+                
+//                // validation: value cardinality constraints
+//                
+//                final int [] cardinality = optdef.getValueCardinality ();
+//                if (opt.getValueCount () < cardinality [0])
+//                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' must have at least " + cardinality [0] +  " value(s)"));
+//                else if (opt.getValueCount () > cardinality [1])
+//                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' must not have more than " + cardinality [1] +  " value(s)"));
+                
+                // validation: "requires" constraints
+                
+                final String [] requires = optdef.getRequiresSet (); // not canonicalized
+                if (requires != null)
+                {
+                    for (int r = 0; r < requires.length; ++ r)
+                    {
+                        if (opts.getOpt (requires [r]) == null)
+                            opts.addError (formatMessage ("option \'" + opt.getName () + "\' requires option \'" + requires [r] +  "\'"));
+                    }
+                }
+                
+                // validation: "not with" constraints
+                
+                final String [] excludes = optdef.getExcludesSet (); // not canonicalized
+                if (excludes != null)
+                {
+                    for (int x = 0; x < excludes.length; ++ x)
+                    {
+                        final Opt xopt = opts.getOpt (excludes [x]);
+                        if (xopt != null)
+                            opts.addError (formatMessage ("option \'" + opt.getName () + "\' cannot be used with option \'" + xopt.getName () +  "\'"));
+                    }
+                }
+                
+                // side effect: determine if usage is requested
+                
+                if (optdef.isUsage ())
+                {
+                    opts.setUsageRequested (opt.getName ().equals (opt.getCanonicalName ()) ? SHORT_USAGE : DETAILED_USAGE);
+                }
+            }
+        }
+        
+        return opts;
+    }
+    
+    private static String getOptCanonicalName (final String n, final OptDef optdef)
+    {
+        if (optdef.isPattern ())
+        {
+            final String canonicalPattern = optdef.getCanonicalName ();
+            final String [] patterns = optdef.getNames ();
+            
+            for (int p = 0; p < patterns.length; ++ p)
+            {
+                final String pattern = patterns [p];
+                
+                if (n.startsWith (pattern))
+                {
+                    return canonicalPattern.concat (n.substring (pattern.length ()));
+                }
+            }
+            
+            // this should never happen:
+            throw new IllegalStateException ("failed to detect pattern prefix for [" + n + "]");
+        }
+        else
+        {
+            return optdef.getCanonicalName ();
+        }
+    }
+    
+    /*
+     * ['optdef' can be null if no current opt def context has been established]
+     * 
+     * pre: av != null
+     * input not validated
+     */
+    private static boolean isOpt (final String av, final int valueCount, final OptDef optdef)
+    {
+        if (optdef != null)
+        {
+            // if the current optdef calls for more values, consume the next token
+            // as an op value greedily, without looking at its prefix:
+        
+            final int [] cardinality = optdef.getValueCardinality ();
+        
+            if (valueCount < cardinality [1]) return false;
+        }
+        
+        // else check av's prefix:
+        
+        for (int p = 0; p < OPT_PREFIXES.length; ++ p)
+        {
+            if (av.startsWith (OPT_PREFIXES [p]))
+                return (av.length () > OPT_PREFIXES [p].length ());
+        }
+        
+        return false;
+    }
+
+    /*
+     * pre: av != null and isOpt(av)=true
+     * input not validated
+     */
+    private static void getOptNameAndValue (final String av, final String [] nv)
+    {
+        nv [0] = null;
+        nv [1] = null;
+        
+        for (int p = 0; p < OPT_PREFIXES.length; ++ p)
+        {
+            if ((av.startsWith (OPT_PREFIXES [p])) && (av.length () > OPT_PREFIXES [p].length ()))
+            {
+                final String name = av.substring (OPT_PREFIXES [p].length ()); // with a possible value after a separator
+                
+                char separator = 0;
+                int sindex = Integer.MAX_VALUE;
+                
+                for (int s = 0; s < OPT_VALUE_SEPARATORS.length; ++ s)
+                {
+                    final int index = name.indexOf (OPT_VALUE_SEPARATORS [s]);
+                    if ((index > 0) && (index < sindex))
+                    {
+                        separator = OPT_VALUE_SEPARATORS [s];
+                        sindex = index;
+                    }
+                }
+                
+                if (separator != 0)
+                {
+                    nv [0] = name.substring (0, sindex);
+                    nv [1] = name.substring (sindex + 1);
+                }
+                else
+                {
+                    nv [0] = name;
+                }
+                
+                return;
+            }
+        }
+    }
+    
+    // protected: .............................................................   
+
+    // package: ...............................................................
+
+
+    static final class Opt implements IOptsParser.IOpt
+    {
+        public String getName ()
+        {
+            return m_name;
+        }
+        
+        public String getCanonicalName ()
+        {
+            return m_canonicalName;
+        }
+        
+        public int getValueCount ()
+        {
+            if (m_values == null) return 0;
+            
+            return m_values.size ();
+        }
+
+        public String getFirstValue ()
+        {
+            if (m_values == null) return null;
+            
+            return (String) m_values.get (0);
+        }
+
+        public String [] getValues ()
+        {
+            if (m_values == null) return IConstants.EMPTY_STRING_ARRAY;
+            
+            final String [] result = new String [m_values.size ()];
+            m_values.toArray (result);
+            
+            return result; 
+        }
+        
+        public String getPatternPrefix ()
+        {
+            return m_patternPrefix;
+        }
+        
+        public String toString ()
+        {
+            final StringBuffer s = new StringBuffer (m_name);
+            if (! m_canonicalName.equals (m_name)) s.append (" [" + m_canonicalName + "]");
+            
+            if (m_values != null)
+            {
+                s.append (": ");
+                s.append (m_values);
+            }
+            
+            return s.toString (); 
+        } 
+        
+        Opt (final String name, final String canonicalName, final String patternPrefix)
+        {
+            m_name = name;
+            m_canonicalName = canonicalName;
+            m_patternPrefix = patternPrefix;
+        }
+        
+        void addValue (final String value)
+        {
+            if (value == null) throw new IllegalArgumentException ("null input: value");
+            
+            if (m_values == null) m_values = new ArrayList ();
+            m_values.add (value);
+        }
+        
+        
+        private final String m_name, m_canonicalName, m_patternPrefix;
+        private ArrayList m_values; 
+
+    } // end of nested class
+    
+    
+    static final class Opts implements IOptsParser.IOpts
+    {
+        public int usageRequestLevel ()
+        {
+            return m_usageRequestLevel; 
+        }
+        
+        public void error (final PrintWriter out, final int width)
+        {
+            // TODO: use width
+            if (hasErrors ())
+            {
+                for (Iterator i = m_errors.iterator (); i.hasNext (); )
+                {
+                    out.println (i.next ());
+                }
+            } 
+        }
+
+        public String [] getFreeArgs ()
+        {
+            if (hasErrors ())
+                throw new IllegalStateException (errorsToString ());
+            
+            return m_freeArgs;
+        }
+
+        public IOpt [] getOpts ()
+        {
+            if (hasErrors ()) return null;
+            
+            if (m_opts.isEmpty ())
+                return EMPTY_OPT_ARRAY;
+            else
+            {
+                final IOpt [] result = new IOpt [m_opts.size ()];
+                m_opts.toArray (result);
+                
+                return result; 
+            }
+        }
+        
+        public IOpt [] getOpts (final String pattern)
+        {
+            if (hasErrors ()) return null;
+            
+            final List /* Opt */ patternOpts = (List) m_patternMap.get (pattern);
+            
+            if ((patternOpts == null) || patternOpts.isEmpty ())
+                return EMPTY_OPT_ARRAY;
+            else
+            {
+                final IOpt [] result = new IOpt [patternOpts.size ()];
+                patternOpts.toArray (result);
+                
+                return result;
+            }
+        }
+
+
+        public boolean hasArg (final String name)
+        {
+            if (hasErrors ())
+                throw new IllegalStateException (errorsToString ());
+            
+            return m_nameMap.containsKey (name);
+        }
+        
+        Opts ()
+        {
+            m_opts = new ArrayList ();
+            m_nameMap = new HashMap ();
+            m_patternMap = new HashMap ();
+        }
+        
+        void addOpt (final Opt opt, final OptDef optdef, final String occuranceName)
+        {
+            if (opt == null) throw new IllegalArgumentException ("null input: opt");
+            if (optdef == null) throw new IllegalArgumentException ("null input: optdef");
+            if (occuranceName == null) throw new IllegalArgumentException ("null input: occuranceName");
+            
+            // [name collisions detected elsewhere]
+            
+            m_opts.add (opt);
+            
+            final String [] names = optdef.getNames ();
+            final boolean isPattern = (opt.getPatternPrefix () != null);
+            
+            if (isPattern)
+            {
+                final String unprefixedName = occuranceName.substring (opt.getPatternPrefix ().length ());
+                
+                for (int n = 0; n < names.length; ++ n)
+                {
+                    m_nameMap.put (names [n].concat (unprefixedName), opt);
+                }
+                
+                {
+                    final String canonicalPattern = optdef.getCanonicalName ();
+                
+                    List patternList = (List) m_patternMap.get (canonicalPattern);
+                    if (patternList == null)
+                    {
+                        patternList = new ArrayList ();
+                        for (int n = 0; n < names.length; ++ n)
+                        {
+                            m_patternMap.put (names [n], patternList);
+                        } 
+                    }
+                    
+                    patternList.add (opt);
+                }
+            }
+            else
+            {
+                for (int n = 0; n < names.length; ++ n)
+                {
+                    m_nameMap.put (names [n], opt);
+                }
+            }
+        }
+        
+        Opt getOpt (final String occuranceName)
+        {
+            if (occuranceName == null) throw new IllegalArgumentException ("null input: occuranceName");
+            
+            return (Opt) m_nameMap.get (occuranceName);
+        }
+        
+        void setFreeArgs (final String [] args, final int start)
+        {
+            if (args == null) throw new IllegalArgumentException ("null input: args");
+            if ((start < 0) || (start > args.length)) throw new IllegalArgumentException ("invalid start index: " + start);
+            
+            m_freeArgs = new String [args.length - start];
+            System.arraycopy (args, start, m_freeArgs, 0, m_freeArgs.length);
+        }
+        
+        void setUsageRequested (final int level)
+        {
+            m_usageRequestLevel = level;
+        }
+        
+        void addError (final String msg)
+        {
+            if (msg != null)
+            {
+                if (m_errors == null) m_errors = new ArrayList ();
+                
+                m_errors.add (msg);
+            }
+        }
+        
+        boolean hasErrors ()
+        {
+            return (m_errors != null) && ! m_errors.isEmpty ();
+        }
+        
+        String errorsToString ()
+        {
+            if (! hasErrors ()) return "<no errors>";
+            
+            final CharArrayWriter caw = new CharArrayWriter ();
+            final PrintWriter pw = new PrintWriter (caw);
+            
+            error (pw, DEFAULT_ERROR_WIDTH);
+            pw.flush ();
+            
+            return caw.toString (); 
+        }
+        
+        
+        private final List /* Opt */ m_opts;
+        private final Map /* String(name/pattern-prefixed name)->Opt */ m_nameMap;
+        private final Map /* String(pattern prefix)->List<Opt> */ m_patternMap;
+        private String [] m_freeArgs;
+        private List /* String */ m_errors;
+        private int m_usageRequestLevel;
+        
+        private static final int DEFAULT_ERROR_WIDTH = 80;
+        private static final IOpt [] EMPTY_OPT_ARRAY = new IOpt [0]; 
+
+    } // end of nested class
+
+
+    static final class OptDef // TODO: merge with Opt?
+    {
+        OptDef (final boolean usage)
+        {
+            m_usage = usage;
+        }
+        
+        boolean isUsage ()
+        {
+            return m_usage;
+        }
+        
+        String getCanonicalName ()
+        {
+            return m_names [0];
+        }
+        
+        String [] getNames ()
+        {
+            return m_names;
+        }
+        
+        boolean isRequired ()
+        {
+            return m_required;
+        }
+        
+        String getValueMnemonic ()
+        {
+            return m_valueMnemonic;
+        }
+        
+        boolean isMergeable ()
+        {
+            return m_mergeable;
+        }
+        
+        boolean isDetailedOnly ()
+        {
+            return m_detailedOnly;
+        }
+        
+        boolean isPattern ()
+        {
+            return m_pattern;
+        }
+        
+        int [] getValueCardinality ()
+        {
+            return m_valueCardinality;
+        }
+        
+        String [] getRequiresSet ()
+        {
+            return m_requiresSet;
+        }
+        
+        String [] getExcludesSet ()
+        {
+            return m_excludesSet;
+        }
+        
+        String getDescription ()
+        {
+            return m_description;
+        }
+        
+        void setNames (final String [] names)
+        {
+            if (names == null) throw new IllegalArgumentException ("null input: names");
+            
+            m_names = names;
+        }
+        
+        void setRequired (final boolean required)
+        {
+            m_required = required;
+        }
+        
+        void setValueMnemonic (final String mnemonic)
+        {
+            if (mnemonic == null) throw new IllegalArgumentException ("null input: mnemonic");
+            
+            m_valueMnemonic = mnemonic;
+        }
+        
+        void setMergeable (final boolean mergeable)
+        {
+            m_mergeable = mergeable;
+        }
+        
+        void setDetailedOnly (final boolean detailedOnly)
+        {
+            m_detailedOnly = detailedOnly;
+        }
+        
+        void setPattern (final boolean pattern)
+        {
+            m_pattern = pattern;
+        }
+        
+        void setValueCardinality (final int [] cardinality)
+        {
+            if ((cardinality == null) || (cardinality.length != 2)) throw new IllegalArgumentException ("null or invalid input: cardinality");
+            
+            m_valueCardinality = cardinality;
+        }
+        
+        void setRequiresSet (final String [] names)
+        {
+            if (names == null) throw new IllegalArgumentException ("null input: names"); 
+            
+            m_requiresSet = names.length > 0 ? names : null;
+        }
+        
+        void setExcludesSet (final String [] names)
+        {
+            if (names == null) throw new IllegalArgumentException ("null input: names");
+            
+            m_excludesSet = names.length > 0 ? names : null;
+        }
+        
+        void setDescription (final String description)
+        {
+            if (description == null) throw new IllegalArgumentException ("null input: description");
+            
+            m_description = description;
+        }
+        
+        
+        static final int [] C_ZERO = new int [] {0, 0};
+        static final int [] C_ONE = new int [] {1, 1};
+        static final int [] C_ZERO_OR_ONE = new int [] {0, 1};
+        static final int [] C_ZERO_OR_MORE = new int [] {0, Integer.MAX_VALUE};
+        static final int [] C_ONE_OR_MORE = new int [] {1, Integer.MAX_VALUE};
+        
+        
+        private final boolean m_usage;
+        private String [] m_names;
+        private boolean m_required;
+        private String m_valueMnemonic;
+        private boolean m_mergeable;
+        private boolean m_detailedOnly;
+        private boolean m_pattern;
+        private int [] m_valueCardinality;
+        private String [] m_requiresSet, m_excludesSet;
+        private String m_description;
+        
+    } // end of nested class
+    
+    
+    static final class OptDefMetadata
+    {
+        OptDefMetadata ()
+        {
+            m_optdefs = new ArrayList ();
+            m_optdefMap = new HashMap ();
+            m_requiredOpts = new HashSet ();
+            m_patternOptDefMap = new HashMap ();
+        }
+        
+        OptDef getOptDef (final String name, final String [] prefixout)
+        {
+            if (name == null) throw new IllegalArgumentException ("null input: name");
+            
+            if (prefixout != null) prefixout [0] = null;
+            
+            // first, see if this is a regular option:
+            OptDef result = (OptDef) m_optdefMap.get (name);
+            
+            // next, see if this is a prefixed option:
+            if (result == null)
+            {
+                for (Iterator ps = m_patternOptDefMap.entrySet ().iterator ();
+                     ps.hasNext (); )
+                {
+                    final Map.Entry entry = (Map.Entry) ps.next ();
+                    final String pattern = (String) entry.getKey ();
+                    
+                    if (name.startsWith (pattern))
+                    {
+                        if (prefixout != null) prefixout [0] = pattern;
+                        result = (OptDef) entry.getValue ();
+                        break;
+                    }
+                }
+            }
+            
+            return result;
+        }
+        
+        Iterator /* OptDef */ getOptDefs ()
+        {
+            return m_optdefs.iterator ();
+        }
+        
+        OptDef getPatternOptDefs (final String pattern) // returns null if no such pattern is defined
+        {
+            if (pattern == null) throw new IllegalArgumentException ("null input: pattern");
+            
+            return (OptDef) m_patternOptDefMap.get (pattern);
+        }
+        
+        Set /* String(canonical name) */ getRequiredOpts ()
+        {
+            return m_requiredOpts;
+        }
+        
+        OptDef getUsageOptDef ()
+        {
+            return m_usageOptDef;
+        }
+        
+        void addOptDef (final OptDef optdef)
+        {
+            if (optdef == null) throw new IllegalArgumentException ("null input: optdef");
+            
+            final Map map = optdef.isPattern () ? m_patternOptDefMap : m_optdefMap;
+            final String [] names = optdef.getNames ();
+            
+            for (int n = 0; n < names.length; ++ n)
+            {
+                if (map.containsKey (names [n]))
+                    throw new IllegalArgumentException ("duplicate option name [" + names [n] + "]");
+                    
+                map.put (names [n], optdef);
+            }
+            
+            m_optdefs.add (optdef);
+            
+            if (optdef.isRequired ())
+                m_requiredOpts.add (optdef.getCanonicalName ());
+            
+            if (optdef.isUsage ())
+            {
+                if (m_usageOptDef != null)
+                    throw new IllegalArgumentException ("usage optdef set already");
+                    
+                m_usageOptDef = optdef;
+            }
+        }
+        
+        
+        final List /* OptDef */ m_optdefs; // keeps the addition order
+        final Map /* String(name)->OptDef */ m_optdefMap;
+        final Set /* String(canonical name) */ m_requiredOpts;
+        final Map /* String(pattern name)->OptDef */ m_patternOptDefMap;
+        private OptDef m_usageOptDef; 
+        
+    } // end of nested class
+
+  
+    static final class MetadataParser
+    {
+        /*
+         * metadata := ( optdef )* <EOF>
+         * 
+         * optdef := optnamelist ":" optmetadata ";"
+         * optnamelist := namelist
+         * optmetadata :=
+         *      ("optional" | "required" )
+         *      [ "," "mergeable" ]
+         *      [ "," "detailedonly" ]
+         *      [ "," "pattern" ] 
+         *      "," "values" ":" cardinality
+         *      [ "," name ]
+         *      [ "," "requires" "{" namelist "}" ]
+         *      [ "," "notwith" "{" namelist "}" ]
+         *      "," text
+         * cardinality := "0" | "1" | "?"
+         * namelist := name ( "," name )*
+         * name := <single quoted string>  
+         * text := <double quoted string> 
+         */         
+         OptDef [] parse (final Reader in)
+         {
+             if (in == null) throw new IllegalArgumentException ("null input: in");             
+             m_in = in;
+             
+             nextChar ();
+             nextToken ();
+             
+             while (m_token != Token.EOF)
+             {
+                 if (m_opts == null) m_opts = new ArrayList ();
+                 m_opts.add (optdef ());
+             }
+             
+             final OptDef [] result;
+             
+             if ((m_opts == null) || (m_opts.size () == 0))
+                result = EMPTY_OPTDEF_ARRAY;
+             else
+             {
+                 result = new OptDef [m_opts.size ()];
+                 m_opts.toArray (result);
+             }
+             
+             m_in = null;
+             m_opts = null;
+             
+             return result;
+         }
+         
+         OptDef optdef ()
+         {
+             final OptDef optdef = new OptDef (false); 
+             
+             optdef.setNames (optnamelist ());
+             accept (Token.COLON_ID);
+             optmetadata (optdef);
+             accept (Token.SEMICOLON_ID);
+             
+             return optdef;
+         }
+         
+         String [] optnamelist ()
+         {
+             return namelist ();
+         }
+         
+         void optmetadata (final OptDef optdef)
+         {
+             switch (m_token.getID ())
+             {
+                 case Token.REQUIRED_ID:
+                 {
+                     accept ();
+                     optdef.setRequired (true);
+                 }
+                 break;
+                 
+                 case Token.OPTIONAL_ID:
+                 {
+                     accept ();
+                     optdef.setRequired (false);
+                 }
+                 break;
+                 
+                 default:
+                    throw new IllegalArgumentException ("parse error: invalid token " + m_token + ", expected " + Token.REQUIRED + " or " + Token.OPTIONAL);
+                 
+             } // end of switch
+             
+             accept (Token.COMMA_ID);
+             
+             if (m_token.getID () == Token.MERGEABLE_ID)
+             {
+                 accept ();
+                 optdef.setMergeable (true);
+                 
+                 accept (Token.COMMA_ID);
+             }
+             
+             if (m_token.getID () == Token.DETAILEDONLY_ID)
+             {
+                 accept ();
+                 optdef.setDetailedOnly (true);
+                 
+                 accept (Token.COMMA_ID);
+             }
+             
+             if (m_token.getID () == Token.PATTERN_ID)
+             {
+                 accept ();
+                 optdef.setPattern (true);
+                 
+                 accept (Token.COMMA_ID);
+             }
+             
+             accept (Token.VALUES_ID);
+             accept (Token.COLON_ID);
+             optdef.setValueCardinality (cardinality ());
+             
+             accept (Token.COMMA_ID);
+             if (m_token.getID () == Token.STRING_ID)
+             {
+                 optdef.setValueMnemonic (m_token.getValue ());
+                 accept ();
+                 
+                 accept (Token.COMMA_ID);
+             }
+             
+             if (m_token.getID () == Token.REQUIRES_ID)
+             {
+                 accept ();
+                 
+                 accept (Token.LBRACKET_ID);
+                 optdef.setRequiresSet (namelist ());
+                 accept (Token.RBRACKET_ID);
+                 
+                 accept (Token.COMMA_ID);
+             }
+             
+             if (m_token.getID () == Token.EXCLUDES_ID)
+             {
+                 accept ();
+                 
+                 accept (Token.LBRACKET_ID);
+                 optdef.setExcludesSet (namelist ());
+                 accept (Token.RBRACKET_ID);
+                 
+                 accept (Token.COMMA_ID);
+             }
+             
+             optdef.setDescription (accept (Token.TEXT_ID).getValue ());
+         }
+         
+         int [] cardinality ()
+         {
+             final Token result = accept (Token.CARD_ID);
+             
+             if ("0".equals (result.getValue ()))
+                return OptDef.C_ZERO;
+             else if ("1".equals (result.getValue ()))
+                return OptDef.C_ONE;
+             else // ?
+                return OptDef.C_ZERO_OR_ONE;
+         }
+         
+         String [] namelist ()
+         {
+             final List _result = new ArrayList ();
+             
+             _result.add (accept (Token.STRING_ID).getValue ());
+             while (m_token.getID () == Token.COMMA_ID)
+             {
+                 accept ();
+                 _result.add (accept (Token.STRING_ID).getValue ());
+             }
+             
+             final String [] result = new String [_result.size ()];
+             _result.toArray (result);
+             
+             return result;
+         }
+         
+         
+         Token accept ()
+         {
+             final Token current = m_token;
+             nextToken ();
+             
+             return current;
+         }
+         
+         Token accept (final int tokenID)
+         {
+             final Token current = m_token;
+             
+             if (m_token.getID () == tokenID)
+                nextToken ();
+             else
+                throw new IllegalArgumentException ("parse error: invalid token [" + m_token + "], expected type [" + tokenID + "]");
+                
+             return current;
+         }
+         
+         // "scanner":
+         
+         void nextToken ()
+         {
+             consumeWS ();
+             
+             switch (m_currentChar)
+             {
+                 case -1: m_token = Token.EOF; break;
+                 
+                 case ':':
+                 {
+                     nextChar ();
+                     m_token = Token.COLON;
+                 }
+                 break;
+                 
+                 case ';':
+                 {
+                     nextChar ();
+                     m_token = Token.SEMICOLON;
+                 }
+                 break;
+                 
+                 case ',':
+                 {
+                     nextChar ();
+                     m_token = Token.COMMA;
+                 }
+                 break;
+                 
+                 case '{':
+                 {
+                     nextChar ();
+                     m_token = Token.LBRACKET;
+                 }
+                 break;
+                 
+                 case '}':
+                 {
+                     nextChar ();
+                     m_token = Token.RBRACKET;
+                 }
+                 break;
+                 
+                 case '0':
+                 {
+                     nextChar ();
+                     m_token = new Token (Token.CARD_ID, "0");
+                 }
+                 break;
+                 
+                 case '1':
+                 {
+                     nextChar ();
+                     m_token = new Token (Token.CARD_ID, "1");
+                 }
+                 break;
+                 
+                 case '?':
+                 {
+                     nextChar ();
+                     m_token = new Token (Token.CARD_ID, "?");
+                 }
+                 break;
+                                 
+                 case '\'':
+                 {
+                     final StringBuffer value = new StringBuffer ();
+                     
+                     nextChar ();
+                     while (m_currentChar != '\'')
+                     {
+                         value.append ((char) m_currentChar);
+                         nextChar ();
+                     }
+                     nextChar ();
+                     
+                     m_token = new Token (Token.STRING_ID, value.toString ());
+                 }
+                 break;
+                 
+                 case '\"':
+                 {
+                     final StringBuffer value = new StringBuffer ();
+                     
+                     nextChar ();
+                     while (m_currentChar != '\"')
+                     {
+                         value.append ((char) m_currentChar);
+                         nextChar ();
+                     }
+                     nextChar ();
+                     
+                     m_token = new Token (Token.TEXT_ID, value.toString ());
+                 }
+                 break;
+                 
+                 default:
+                 {
+                     final StringBuffer value = new StringBuffer ();
+                     
+                     while (Character.isLetter ((char) m_currentChar))
+                     {
+                         value.append ((char) m_currentChar);
+                         nextChar ();
+                     }
+                     
+                     final Token token = (Token) KEYWORDS.get (value.toString ());
+                     if (token == null)
+                        throw new IllegalArgumentException ("parse error: unrecognized keyword [" + value  + "]");
+                     
+                     m_token = token;
+                 }
+                                  
+             } // end of switch
+         }
+         
+         
+         private void consumeWS ()
+         {
+             if (m_currentChar == -1)
+                return;
+             else
+             {
+                 while (Character.isWhitespace ((char) m_currentChar))
+                 {
+                    nextChar ();
+                 }
+             }
+
+             // TODO: #-comments
+         }
+         
+         private void nextChar ()
+         {
+             try
+             {
+                m_currentChar = m_in.read ();
+             }
+             catch (IOException ioe)
+             {
+                 throw new RuntimeException ("I/O error while parsing: " + ioe); 
+             }
+         }
+         
+         
+         private Reader m_in;
+         private List m_opts;
+         
+         private Token m_token;
+         private int m_currentChar;
+         
+         private static final Map KEYWORDS;
+         
+         private static final OptDef [] EMPTY_OPTDEF_ARRAY = new OptDef [0];
+         
+         static
+         {
+             KEYWORDS = new HashMap (17);
+             
+             KEYWORDS.put (Token.OPTIONAL.getValue (), Token.OPTIONAL);
+             KEYWORDS.put (Token.REQUIRED.getValue (), Token.REQUIRED);
+             KEYWORDS.put (Token.VALUES.getValue (), Token.VALUES);
+             KEYWORDS.put (Token.REQUIRES.getValue (), Token.REQUIRES);
+             KEYWORDS.put (Token.EXCLUDES.getValue (), Token.EXCLUDES);
+             KEYWORDS.put (Token.MERGEABLE.getValue (), Token.MERGEABLE);
+             KEYWORDS.put (Token.DETAILEDONLY.getValue (), Token.DETAILEDONLY);
+             KEYWORDS.put (Token.PATTERN.getValue (), Token.PATTERN);
+         }
+        
+    } // end of nested class
+    
+    
+    OptsParser (final String metadataResourceName, final ClassLoader loader, final String [] usageOpts)
+    {
+        this (metadataResourceName, loader, null, usageOpts);
+    }
+    
+    OptsParser (final String metadataResourceName, final ClassLoader loader, final String msgPrefix, final String [] usageOpts)
+    {
+        if (metadataResourceName == null) throw new IllegalArgumentException ("null input: metadataResourceName");
+        
+        m_msgPrefix = msgPrefix;
+        
+        InputStream in = null;
+        try
+        {
+            in = ResourceLoader.getResourceAsStream (metadataResourceName, loader);
+            if (in == null)
+                throw new IllegalArgumentException ("resource [" + metadataResourceName + "] could not be loaded via [" + loader + "]");
+            
+            // TODO: encoding
+            final Reader rin = new InputStreamReader (in);
+            
+            m_metadata = parseOptDefMetadata (rin, usageOpts);   
+        }
+        finally
+        {
+            if (in != null) try { in.close (); } catch (IOException ignore) {} 
+        }
+    }
+    
+    // private: ...............................................................
+
+
+    private static final class Token
+    {
+        Token (final int ID, final String value)
+        {
+            if (value == null) throw new IllegalArgumentException ("null input: value");
+            
+            m_ID = ID;
+            m_value = value;
+        }
+        
+        int getID ()
+        {
+            return m_ID;
+        }
+        
+        String getValue ()
+        {
+            return m_value;
+        }
+        
+        public String toString ()
+        {
+            return m_ID + ": [" + m_value + "]";
+        }
+        
+        
+        static final int EOF_ID = 0;
+        static final int STRING_ID = 1;
+        static final int COLON_ID = 2;
+        static final int SEMICOLON_ID = 3;
+        static final int COMMA_ID = 4;
+        static final int LBRACKET_ID = 5;
+        static final int RBRACKET_ID = 6;
+        static final int OPTIONAL_ID = 7;
+        static final int REQUIRED_ID = 8;
+        static final int CARD_ID = 9;
+        static final int VALUES_ID = 10;
+        static final int TEXT_ID = 11;
+        static final int REQUIRES_ID = 12;
+        static final int EXCLUDES_ID = 13;
+        static final int MERGEABLE_ID = 14;
+        static final int DETAILEDONLY_ID = 15;
+        static final int PATTERN_ID = 16;
+        
+        static final Token EOF = new Token (EOF_ID, "<EOF>");
+        static final Token COLON = new Token (COLON_ID, ":");
+        static final Token SEMICOLON = new Token (SEMICOLON_ID, ";");
+        static final Token COMMA = new Token (COMMA_ID, ",");
+        static final Token LBRACKET = new Token (LBRACKET_ID, "{");
+        static final Token RBRACKET = new Token (RBRACKET_ID, "}");
+        static final Token OPTIONAL = new Token (OPTIONAL_ID, "optional");
+        static final Token REQUIRED = new Token (REQUIRED_ID, "required");
+        static final Token VALUES = new Token (VALUES_ID, "values");
+        static final Token REQUIRES = new Token (REQUIRES_ID, "requires");
+        static final Token EXCLUDES = new Token (EXCLUDES_ID, "excludes");
+        static final Token MERGEABLE = new Token (MERGEABLE_ID, "mergeable");
+        static final Token DETAILEDONLY = new Token (DETAILEDONLY_ID, "detailedonly");
+        static final Token PATTERN = new Token (PATTERN_ID, "pattern");
+        
+        private final int m_ID;
+        private final String m_value;
+        
+    } // end of nested class
+    
+    
+    private static OptDefMetadata parseOptDefMetadata (final Reader in, final String [] usageOpts)
+    {
+        final MetadataParser parser = new MetadataParser ();
+        final OptDef [] optdefs = parser.parse (in);
+        
+        // validate:
+        
+//        for (int o = 0; o < optdefs.length; ++ o)
+//        {
+//            final OptDef optdef = optdefs [o];
+//            final int [] cardinality = optdef.getValueCardinality ();
+//            
+//            if (optdef.isMergeable ())
+//            {
+//                if ((cardinality [1] != 0) && (cardinality [1] != Integer.MAX_VALUE))
+//                    throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] is mergeable and can only specify {0, +inf} for max value cardinality: " + cardinality [1]); 
+//            }            
+//        } 
+         
+        final OptDefMetadata result = new OptDefMetadata ();
+        for (int o = 0; o < optdefs.length; ++ o)
+        {
+            result.addOptDef (optdefs [o]);
+        }
+        
+        // add usage opts:
+        if (usageOpts != null)
+        {
+            final OptDef usage = new OptDef (true);
+            
+            usage.setNames (usageOpts);
+            usage.setDescription ("display usage information");
+            usage.setValueCardinality (OptDef.C_ZERO);
+            usage.setRequired (false);
+            usage.setDetailedOnly (false);
+            usage.setMergeable (false);
+            
+            result.addOptDef (usage);
+        }
+        
+        // TODO: fix this to be pattern-savvy
+        
+        for (int o = 0; o < optdefs.length; ++ o)
+        {
+            final OptDef optdef = optdefs [o];
+            
+            final String [] requires = optdef.getRequiresSet ();
+            if (requires != null)
+            {
+                for (int r = 0; r < requires.length; ++ r)
+                {
+                    final OptDef ropt = result.getOptDef (requires [r], null);
+                    if (ropt == null)
+                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + requires [r] + "] in its \'requires\' set");
+                    
+                    if (ropt == optdef)
+                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'requires\' set");
+                }
+            }
+            
+            final String [] excludes = optdef.getExcludesSet ();
+            if (excludes != null)
+            {
+                for (int x = 0; x < excludes.length; ++ x)
+                {
+                    final OptDef xopt = result.getOptDef (excludes [x], null);
+                    if (xopt == null)
+                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + excludes [x] + "] in its \'excludes\' set");
+                    
+                    if (xopt.isRequired ())
+                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies a required option [" + excludes [x] + "] in its \'excludes\' set");
+                    
+                    if (xopt == optdef)
+                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'excludes\' set");
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    private String formatMessage (final String msg)
+    {
+        if (m_msgPrefix == null) return msg;
+        else
+        {
+            return m_msgPrefix.concat (msg);
+        }
+    }
+    
+    
+    private final String m_msgPrefix;
+    private final OptDefMetadata m_metadata;
+    
+    private static final int CANONICAL_OPT_PREFIX = 1; // indexes into OPT_PREFIXES
+    private static final String [] OPT_PREFIXES = new String [] {"--", "-"}; // HACK: these must appear in decreasing length order
+    private static final char [] OPT_VALUE_SEPARATORS = new char [] {':', '='};
+    
+    private static final int STATE_OPT = 0, STATE_OPT_VALUE = 1, STATE_FREE_ARGS = 2, STATE_ERROR = 3;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/asserts/$assert.java b/core/java12/com/vladium/util/asserts/$assert.java
new file mode 100644
index 0000000..9160f08
--- /dev/null
+++ b/core/java12/com/vladium/util/asserts/$assert.java
@@ -0,0 +1,60 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: \044assert.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $
+ */
+package com.vladium.util.asserts;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2001
+ */
+public
+abstract class $assert
+{
+    // public: ................................................................
+    
+    
+    // TODO: set to false for release
+    
+    /**
+     * Global compile time assertion flag.
+     */
+    public static final boolean ENABLED = false;
+    
+    /**
+     * 
+     * @param condition
+     * @param msg
+     */
+    public static void ASSERT (final boolean condition, final String msg)
+    {
+        if (ENABLED)
+        {
+            if (! condition) throw new RuntimeException (msg);
+        }
+    }
+    
+    public static void ASSERT (final boolean condition)
+    {
+        if (ENABLED)
+        {
+            if (! condition) throw new RuntimeException ("ASSERTION FAILURE");
+        }
+    }
+    
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private $assert () {} // prevent subclassing
+
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/util/exception/AbstractException.java b/core/java12/com/vladium/util/exception/AbstractException.java
new file mode 100644
index 0000000..ab6f9d6
--- /dev/null
+++ b/core/java12/com/vladium/util/exception/AbstractException.java
@@ -0,0 +1,329 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AbstractException.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $
+ */
+package com.vladium.util.exception;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+// ----------------------------------------------------------------------------
+/**
+ * Based on code published by me in <a href="http://www.fawcette.com/javapro/2002_12/online/exception_vroubtsov_12_16_02/default_pf.asp">JavaPro, 2002</a>.<P>
+ * 
+ * This checked exception class is designed as a base/expansion point for the
+ * entire hierarchy of checked exceptions in a project.<P>
+ * 
+ * It provides the following features:
+ * <UL>
+ *    <LI> ability to take in compact error codes that map to full text messages
+ *    in a resource bundle loaded at this class' instantiation time. This avoids
+ *    hardcoding the error messages in product code and allows for easy
+ *    localization of such text if required. Additionally, these messages
+ *    can be parametrized in the java.text.MessageFormat style;
+ *    <LI> exception chaining in J2SE versions prior to 1.4
+ * </UL>
+ * 
+ * See {@link AbstractRuntimeException} for an unchecked version of the same class.<P> 
+ *
+ * TODO: javadoc
+ *
+ * Each constructor that accepts a String 'message' parameter accepts an error
+ * code as well. You are then responsible for ensuring that either the root
+ * <CODE>com.vladium.exception.exceptions</CODE> resource bundle
+ * or your project/exception class-specific resource bundle [see
+ * <A HREF="#details">below</A> for details] contains a mapping for this error
+ * code. When this lookup fails the passed String value itself will be used as
+ * the error message.<P>
+ *
+ * All constructors taking an 'arguments' parameter supply parameters to the error
+ * message used as a java.text.MessageFormat pattern.<P>
+ * 
+ * Example:
+ * <PRE><CODE>
+ *  File file = ...
+ *  try
+ *  ...
+ *  catch (Exception e)
+ *  {
+ *      throw new AbstractException ("FILE_NOT_FOUND", new Object[] {file, e}, e);
+ *  }
+ * </CODE></PRE>
+ *      where <CODE>com.vladium.util.exception.exceptions</CODE> contains:
+ * <PRE><CODE>
+ * FILE_NOT_FOUND: file {0} could not be opened: {1}
+ * </CODE></PRE>
+ *
+ * To log exception data use {@link #getMessage} or <CODE>printStackTrace</CODE>
+ * family of methods. You should never have to use toString().<P>
+ *
+ * <A NAME="details"> It is also possible to use project- or exception
+ * subhierarchy-specific message resource bundles without maintaining all error
+ * codes in <CODE>com.vladium.exception.exceptions</CODE>. To do so, create a
+ * custom resource bundle and add the following static initializer code to your
+ * base exception class:
+ * <PRE><CODE>
+ *  static
+ *  {
+ *      addExceptionResource (MyException.class, "my_custom_resource_bundle");
+ *  }
+ * </CODE></PRE>
+ * The bundle name is relative to MyException package. This step can omitted if
+ * the bundle name is "exceptions".
+ * 
+ * Note that the implementation correctly resolves error code name collisions
+ * across independently developed exception families, as long as resource bundles
+ * use unique names. Specifically, error codes follow inheritance and hiding rules
+ * similar to Java class static methods. See {@link ExceptionCommon#addExceptionResource}
+ * for further details.
+ *
+ * @author Vlad Roubtsov, (C) 2002
+ */
+public
+abstract class AbstractException extends Exception implements ICodedException, IThrowableWrapper
+{
+    // public: ................................................................
+
+    /**
+     * Constructs an exception with null message and null cause.
+     */    
+    public AbstractException ()
+    {
+        m_cause = null;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *
+     * @param message the detail message [can be null]
+     */
+    public AbstractException (final String message)
+    {
+        super (message);
+        m_cause = null;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *   
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     *
+     * @see java.text.MessageFormat
+     */
+    public AbstractException (final String message, final Object [] arguments)
+    {
+        super (message);
+        m_cause = null;
+        m_arguments = arguments == null ? null : (Object []) arguments.clone ();
+    }
+    
+    /**
+     * Constructs an exception with null error message/code and given cause.
+     *
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public AbstractException (final Throwable cause)
+    {
+        super ();
+        m_cause = cause;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public AbstractException (final String message, final Throwable cause)
+    {
+        super (message);
+        m_cause = cause;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     * @param cause the cause [nested exception] [can be null]
+     *
+     * @see java.text.MessageFormat
+     */
+    public AbstractException (final String message, final Object [] arguments, final Throwable cause)
+    {
+        super (message);
+        m_cause = cause;
+        m_arguments = arguments == null ? null : (Object []) arguments.clone ();
+    }
+    
+
+    /**
+     * Overrides base method to support error code lookup and avoid returning nulls.
+     * Note that this does not recurse into any 'cause' for message lookup, it only
+     * uses the data passed into the constructor. Subclasses cannot override.<P>
+     *
+     * Equivalent to {@link #getLocalizedMessage}.
+     *
+     * @return String error message provided at construction time or the result
+     * of toString() if no/null message was provided [never null].
+     */  
+    public final String getMessage ()
+    {
+        if (m_message == null) // not synchronized by design
+        {
+            String msg;
+            final String supermsg = super.getMessage ();
+            final Class _class = getClass ();
+            
+            if (m_arguments == null)
+            {
+                msg = ExceptionCommon.getMessage (_class, supermsg);
+            }
+            else
+            {
+                msg = ExceptionCommon.getMessage (_class, supermsg, m_arguments);
+            }
+            
+            if (msg == null)
+            {
+                // this is the same as what's done in Throwable.toString() [copied here to be independent of future JDK changes]
+                final String className = _class.getName ();
+                
+                msg = (supermsg != null) ? (className + ": " + supermsg) : className;
+            }
+            
+            m_message = msg;
+        }
+        
+        return m_message;
+    }
+    
+    /**
+     * Overrides base method for the sole purpose of making it final.<P>
+     * 
+     * Equivalent to {@link #getMessage}.
+     */
+    public final String getLocalizedMessage ()
+    {
+        // this is the same as what's done in Throwable
+        // [copied here to be independent of future JDK changes]
+        return getMessage ();
+    }
+
+    /**
+     * Overrides Exception.printStackTrace() to (a) force the output to go
+     * to System.out and (b) handle nested exceptions in JDKs prior to 1.4.<P>
+     * 
+     * Subclasses cannot override.
+     */    
+    public final void printStackTrace ()
+    {
+        // NOTE: unlike the JDK implementation, force the output to go to System.out:
+        ExceptionCommon.printStackTrace (this, System.out);
+    }
+    
+    /**
+     * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P>
+     * 
+     * Subclasses cannot override.
+     */    
+    public final void printStackTrace (final PrintStream s)
+    {
+        ExceptionCommon.printStackTrace (this, s);
+    }
+    
+    /**
+     * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P>
+     * 
+     * Subclasses cannot override.
+     */ 
+    public final void printStackTrace (final PrintWriter s)
+    {
+        ExceptionCommon.printStackTrace (this, s);
+    }
+
+    // ICodedException:
+    
+    /**
+     * Returns the String that was passed as 'message' constructor argument.
+     * Can be null.
+     *
+     * @return message code string [can be null]
+     */
+    public final String getErrorCode ()
+    {
+        return super.getMessage ();
+    }
+
+    // IThrowableWrapper:
+    
+    /**
+     * This implements {@link IThrowableWrapper}
+     * and also overrides the base method in JDK 1.4+.
+     */
+    public final Throwable getCause ()
+    {
+        return m_cause;
+    }
+
+    public void __printStackTrace (final PrintStream ps)
+    {
+        super.printStackTrace (ps);
+    }
+    
+    public void __printStackTrace (final PrintWriter pw)
+    {
+        super.printStackTrace (pw);
+    }
+
+    /**
+     * Equivalent to {@link ExceptionCommon#addExceptionResource}, repeated here for
+     * convenience. Subclasses should invoke from static initializers <I>only</I>.
+     * 'namespace' should be YourException.class.
+     */
+    public static void addExceptionResource (final Class namespace,
+                                             final String messageResourceBundleName)
+    {
+        // note: 'namespace' will be the most derived class; it is possible to
+        // auto-detect that in a static method but that requires some security
+        // permissions
+        ExceptionCommon.addExceptionResource (namespace, messageResourceBundleName);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+
+    /*
+     * Ensures that this instance can be serialized even if some message parameters
+     * are not serializable objects.
+     */
+    private void writeObject (final ObjectOutputStream out)
+        throws IOException
+    {
+        getMessage (); // transform this instance to serializable form
+        out.defaultWriteObject ();
+    }
+
+    
+    private String m_message; // marshalled/cached result of getMessage()
+    private transient final Object [] m_arguments;
+    // note: this field duplicates functionality available in stock Throwable in JRE 1.4+
+    private final Throwable m_cause;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/util/exception/AbstractRuntimeException.java b/core/java12/com/vladium/util/exception/AbstractRuntimeException.java
new file mode 100644
index 0000000..66190d8
--- /dev/null
+++ b/core/java12/com/vladium/util/exception/AbstractRuntimeException.java
@@ -0,0 +1,329 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: AbstractRuntimeException.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $
+ */
+package com.vladium.util.exception;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+// ----------------------------------------------------------------------------
+/**
+ * Based on code published by me in <a href="http://www.fawcette.com/javapro/2002_12/online/exception_vroubtsov_12_16_02/default_pf.asp">JavaPro, 2002</a>.<P>
+ * 
+ * This unchecked exception class is designed as a base/expansion point for the
+ * entire hierarchy of unchecked exceptions in a project.<P>
+ * 
+ * It provides the following features:
+ * <UL>
+ *    <LI> ability to take in compact error codes that map to full text messages
+ *    in a resource bundle loaded at this class' instantiation time. This avoids
+ *    hardcoding the error messages in product code and allows for easy
+ *    localization of such text if required. Additionally, these messages
+ *    can be parametrized in the java.text.MessageFormat style;
+ *    <LI> exception chaining in J2SE versions prior to 1.4
+ * </UL>
+ * 
+ * See {@link AbstractException} for a checked version of the same class.<P> 
+ * 
+ * TODO: javadoc
+ *
+ * Each constructor that accepts a String 'message' parameter accepts an error
+ * code as well. You are then responsible for ensuring that either the root
+ * <CODE>com.vladium.exception.exceptions</CODE> resource bundle
+ * or your project/exception class-specific resource bundle [see
+ * <A HREF="#details">below</A> for details] contains a mapping for this error
+ * code. When this lookup fails the passed String value itself will be used as
+ * the error message.<P>
+ *
+ * All constructors taking an 'arguments' parameter supply parameters to the error
+ * message used as a java.text.MessageFormat pattern.<P>
+ * 
+ * Example:
+ * <PRE><CODE>
+ *  File file = ...
+ *  try
+ *  ...
+ *  catch (Exception e)
+ *  {
+ *      throw new AbstractRuntimeException ("FILE_NOT_FOUND", new Object[] {file, e}, e);
+ *  }
+ * </CODE></PRE>
+ *      where <CODE>com.vladium.util.exception.exceptions</CODE> contains:
+ * <PRE><CODE>
+ * FILE_NOT_FOUND: file {0} could not be opened: {1}
+ * </CODE></PRE>
+ *
+ * To log exception data use {@link #getMessage} or <CODE>printStackTrace</CODE>
+ * family of methods. You should never have to use toString().<P>
+ *
+ * <A NAME="details"> It is also possible to use project- or exception
+ * subhierarchy-specific message resource bundles without maintaining all error
+ * codes in <CODE>com.vladium.exception.exceptions</CODE>. To do so, create a
+ * custom resource bundle and add the following static initializer code to your
+ * base exception class:
+ * <PRE><CODE>
+ *  static
+ *  {
+ *      addExceptionResource (MyException.class, "my_custom_resource_bundle");
+ *  }
+ * </CODE></PRE>
+ * The bundle name is relative to MyException package. This step can omitted if
+ * the bundle name is "exceptions".
+ * 
+ * Note that the implementation correctly resolves error code name collisions
+ * across independently developed exception families, as long as resource bundles
+ * use unique names. Specifically, error codes follow inheritance and hiding rules
+ * similar to Java class static methods. See {@link ExceptionCommon#addExceptionResource}
+ * for further details.
+ *
+ * @author Vlad Roubtsov, (C) 2002
+ */
+public
+abstract class AbstractRuntimeException extends RuntimeException implements ICodedException, IThrowableWrapper
+{
+    // public: ................................................................
+
+    /**
+     * Constructs an exception with null message and null cause.
+     */    
+    public AbstractRuntimeException ()
+    {
+        m_cause = null;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *
+     * @param message the detail message [can be null]
+     */
+    public AbstractRuntimeException (final String message)
+    {
+        super (message);
+        m_cause = null;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and null cause.
+     *   
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     *
+     * @see java.text.MessageFormat
+     */
+    public AbstractRuntimeException (final String message, final Object [] arguments)
+    {
+        super (message);
+        m_cause = null;
+        m_arguments = arguments == null ? null : (Object []) arguments.clone ();
+    }
+    
+    /**
+     * Constructs an exception with null error message/code and given cause.
+     *
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public AbstractRuntimeException (final Throwable cause)
+    {
+        super ();
+        m_cause = cause;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param cause the cause [nested exception] [can be null]
+     */
+    public AbstractRuntimeException (final String message, final Throwable cause)
+    {
+        super (message);
+        m_cause = cause;
+        m_arguments = null;
+    }
+    
+    /**
+     * Constructs an exception with given error message/code and given cause.
+     *
+     * @param message the detail message [can be null]
+     * @param arguments message format parameters [can be null or empty]
+     * @param cause the cause [nested exception] [can be null]
+     *
+     * @see java.text.MessageFormat
+     */
+    public AbstractRuntimeException (final String message, final Object [] arguments, final Throwable cause)
+    {
+        super (message);
+        m_cause = cause;
+        m_arguments = arguments == null ? null : (Object []) arguments.clone ();
+    }
+    
+
+    /**
+     * Overrides base method to support error code lookup and avoid returning nulls.
+     * Note that this does not recurse into any 'cause' for message lookup, it only
+     * uses the data passed into the constructor. Subclasses cannot override.<P>
+     *
+     * Equivalent to {@link #getLocalizedMessage}.
+     *
+     * @return String error message provided at construction time or the result
+     * of toString() if no/null message was provided [never null].
+     */  
+    public final String getMessage ()
+    {
+        if (m_message == null) // not synchronized by design
+        {
+            String msg;
+            final String supermsg = super.getMessage ();
+            final Class _class = getClass ();
+            
+            if (m_arguments == null)
+            {
+                msg = ExceptionCommon.getMessage (_class, supermsg);
+            }
+            else
+            {
+                msg = ExceptionCommon.getMessage (_class, supermsg, m_arguments);
+            }
+            
+            if (msg == null)
+            {
+                // this is the same as what's done in Throwable.toString() [copied here to be independent of future JDK changes]
+                final String className = _class.getName ();
+                
+                msg = (supermsg != null) ? (className + ": " + supermsg) : className;
+            }
+            
+            m_message = msg;
+        }
+        
+        return m_message;
+    }
+    
+    /**
+     * Overrides base method for the sole purpose of making it final.<P>
+     * 
+     * Equivalent to {@link #getMessage}.
+     */
+    public final String getLocalizedMessage ()
+    {
+        // this is the same as what's done in Throwable
+        // [copied here to be independent of future JDK changes]
+        return getMessage ();
+    }
+
+    /**
+     * Overrides Exception.printStackTrace() to (a) force the output to go
+     * to System.out and (b) handle nested exceptions in JDKs prior to 1.4.<P>
+     * 
+     * Subclasses cannot override.
+     */    
+    public final void printStackTrace ()
+    {
+        // NOTE: unlike the JDK implementation, force the output to go to System.out:
+        ExceptionCommon.printStackTrace (this, System.out);
+    }
+    
+    /**
+     * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P>
+     * 
+     * Subclasses cannot override.
+     */    
+    public final void printStackTrace (final PrintStream s)
+    {
+        ExceptionCommon.printStackTrace (this, s);
+    }
+    
+    /**
+     * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P>
+     * 
+     * Subclasses cannot override.
+     */ 
+    public final void printStackTrace (final PrintWriter s)
+    {
+        ExceptionCommon.printStackTrace (this, s);
+    }
+
+    // ICodedException:
+    
+    /**
+     * Returns the String that was passed as 'message' constructor argument.
+     * Can be null.
+     *
+     * @return message code string [can be null]
+     */
+    public final String getErrorCode ()
+    {
+        return super.getMessage ();
+    }
+
+    // IThrowableWrapper:
+    
+    /**
+     * This implements {@link IThrowableWrapper}
+     * and also overrides the base method in JDK 1.4+.
+     */
+    public final Throwable getCause ()
+    {
+        return m_cause;
+    }
+
+    public void __printStackTrace (final PrintStream ps)
+    {
+        super.printStackTrace (ps);
+    }
+    
+    public void __printStackTrace (final PrintWriter pw)
+    {
+        super.printStackTrace (pw);
+    }
+
+    /**
+     * Equivalent to {@link ExceptionCommon#addExceptionResource}, repeated here for
+     * convenience. Subclasses should invoke from static initializers <I>only</I>.
+     * 'namespace' should be YourException.class.
+     */
+    public static void addExceptionResource (final Class namespace,
+                                             final String messageResourceBundleName)
+    {
+        // note: 'namespace' will be the most derived class; it is possible to
+        // auto-detect that in a static method but that requires some security
+        // permissions
+        ExceptionCommon.addExceptionResource (namespace, messageResourceBundleName);
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+
+
+    /*
+     * Ensures that this instance can be serialized even if some message parameters
+     * are not serializable objects.
+     */
+    private void writeObject (final ObjectOutputStream out)
+        throws IOException
+    {
+        getMessage (); // transform this instance to serializable form
+        out.defaultWriteObject ();
+    }
+
+    
+    private String m_message; // marshalled/cached result of getMessage()
+    private transient final Object [] m_arguments;
+    // note: this field duplicates functionality available in stock Throwable in JRE 1.4+
+    private final Throwable m_cause;
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/util/exception/ExceptionCommon.java b/core/java12/com/vladium/util/exception/ExceptionCommon.java
new file mode 100644
index 0000000..5fa98f7
--- /dev/null
+++ b/core/java12/com/vladium/util/exception/ExceptionCommon.java
@@ -0,0 +1,503 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ExceptionCommon.java,v 1.1.1.1.2.2 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.util.exception;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import com.vladium.util.IJREVersion;
+
+// TODO: embed build # in error codes
+
+// ----------------------------------------------------------------------------
+/**
+ * TODO: javadoc
+ * 
+ * Based on code <a href="http://www.fawcette.com/javapro/2002_12/online/exception_vroubtsov_12_16_02/default_pf.asp">published</a>
+ * by me in JavaPro, 2002.<P>
+ * 
+ * This non-instantiable class provides static support functions common to
+ * {@link AbstractException} and {@link AbstractRuntimeException}.<P>
+ *
+ * @author Vlad Roubtsov, (C) 2002
+ */
+abstract class ExceptionCommon implements IJREVersion
+{
+    // public: ................................................................
+               
+    /**
+     * This method can be called by static initializers of {@link AbstractException}
+     * and {@link AbstractRuntimeException} subclasses in order to add another
+     * resource bundle to the set that is used to look up error codes. This makes
+     * it possible to extend the set of exception error codes across independently
+     * maintained and built projects.<P>
+     *
+     * <BLOCKQUOTE>
+     * Note that this introduces a possibility of error code name clashes. This
+     * is resolved in the following way:
+     * <UL>
+     *     <LI> when <CODE>getMessage(namespace, code)</CODE> is called, 'code'
+     *     is attempted to be looked up in the resource bundle previously keyed
+     *     under 'namespace';
+     *
+     *     <LI> if no such bundle it found or if it does not contain a value for
+     *     key 'code', the same step is repeated for the superclass of 'namespace';
+     *
+     *     <LI> finally, if all of the above steps fail, the root resource bundle
+     *     specified by {@link #ROOT_RESOURCE_BUNDLE_NAME} is searched.
+     * </UL>
+     * 
+     * This strategy ensures that error codes follow inheritance and hiding rules
+     * similar to Java static methods.<P>
+     * </BLOCKQUOTE>
+     *
+     * <B>IMPORTANT:</B> this method must be called from static class initializers
+     * <I>only</I>.<P>
+     *
+     * There is no visible state change if the indicated resource is not found
+     * or if it has been added already under the same key.<P>
+     *
+     * @param namespace the Class object acting as the namespace key for the
+     * resource bundle identified by 'messageResourceBundleName'. <I>This should
+     * be the calling class.</I> [the method is a no-op if this is null]
+     *
+     * @param messageResourceBundleName name of a bundle (path relative to 'namespace'
+     * package) to add to the set from which error code mappings are retrieved
+     * [the method is a no-op if this is null or an empty string]
+     * 
+     * @return ResourceBundle that corresponds to 'namespace' key or null if
+     * no such bundle could be loaded
+     *
+     * @throws Error if 'namespace' does not correspond to an exception class derived
+     * from {@link AbstractException} or {@link AbstractRuntimeException}.
+     *
+     * @see #lookup
+     */
+    public static ResourceBundle addExceptionResource (final Class namespace,
+                                                       final String messageResourceBundleName)
+    {
+        if ((namespace != null) && (messageResourceBundleName != null)
+            && (messageResourceBundleName.length () > 0))
+        {
+            // bail out if the some other exception hierarchy attempts
+            // to use this functionality:
+            if (! ABSTRACT_EXCEPTION.isAssignableFrom (namespace)
+                && ! ABSTACT_RUNTIME_EXCEPTION.isAssignableFrom (namespace))
+            {
+                throw new Error ("addExceptionResource(): class [" + namespace +
+                    "] is not a subclass of " + ABSTRACT_EXCEPTION.getName () +
+                    " or " + ABSTACT_RUNTIME_EXCEPTION.getName ());
+            }
+            
+            // try to load resource bundle
+            
+            ResourceBundle temprb = null;
+            String nameInNamespace = null;
+            try
+            {
+                nameInNamespace = getNameInNamespace (namespace, messageResourceBundleName);
+                
+                //temprb = ResourceBundle.getBundle (nameInNamespace);
+                
+                ClassLoader loader = namespace.getClassLoader ();
+                if (loader == null) loader = ClassLoader.getSystemClassLoader ();
+                
+                temprb = ResourceBundle.getBundle (nameInNamespace, Locale.getDefault (), loader);
+            }
+            catch (Throwable ignore)
+            {
+               // ignored intentionally: if the exception codes rb is absent,
+               // we are still in a supported configuration
+               temprb = null;
+            }
+            
+            if (temprb != null)
+            {
+                synchronized (s_exceptionCodeMap)
+                {
+                    final ResourceBundle currentrb =
+                        (ResourceBundle) s_exceptionCodeMap.get (namespace); 
+                    if (currentrb != null)
+                        return currentrb;
+                    else
+                    {
+                        s_exceptionCodeMap.put (namespace, temprb);
+                        return temprb;
+                    }
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................  
+
+
+    static void printStackTrace (Throwable t, final PrintWriter out)
+    {
+        if (JRE_1_4_PLUS)
+        {
+            if (t instanceof IThrowableWrapper)
+            {
+                final IThrowableWrapper tw = (IThrowableWrapper) t;
+                
+                tw.__printStackTrace (out); 
+            }
+            else
+            {
+                t.printStackTrace (out);
+            }
+        }
+        else
+        {
+            for (boolean first = true; t != null; )
+            {
+                if (first)
+                    first = false;
+                else
+                {
+                    out.println ();
+                    out.println (NESTED_THROWABLE_HEADER);                
+                }
+                
+                if (t instanceof IThrowableWrapper)
+                {
+                    final IThrowableWrapper tw = (IThrowableWrapper) t;
+                    
+                    tw.__printStackTrace (out);
+                    t = tw.getCause (); 
+                }
+                else
+                {
+                    t.printStackTrace (out);
+                    break;
+                }
+            }
+        }
+    }
+    
+    
+    static void printStackTrace (Throwable t, final PrintStream out)
+    {
+        if (JRE_1_4_PLUS)
+        {
+            if (t instanceof IThrowableWrapper)
+            {
+                final IThrowableWrapper tw = (IThrowableWrapper) t;
+                
+                tw.__printStackTrace (out); 
+            }
+            else
+            {
+                t.printStackTrace (out);
+            }
+        }
+        else
+        {
+            for (boolean first = true; t != null; )
+            {
+                if (first)
+                    first = false;
+                else
+                {
+                    out.println ();
+                    out.println (NESTED_THROWABLE_HEADER);                
+                }
+                
+                if (t instanceof IThrowableWrapper)
+                {
+                    final IThrowableWrapper tw = (IThrowableWrapper) t;
+                    
+                    tw.__printStackTrace (out);
+                    t = tw.getCause (); 
+                }
+                else
+                {
+                    t.printStackTrace (out);
+                    break;
+                }
+            }
+        }
+    }
+    
+
+    /**
+     * Provides support for lookup of exception error codes from {@link AbstractException}
+     * and {@link AbstractRuntimeException} and their subclasses.
+     *
+     * @param namespace the Class object acting as the key to the namespace from
+     * which to retrieve the description for 'code' [can be null, in which case
+     * only the root namespace is used for lookup]
+     *
+     * @param code the message string value that was passed into exception
+     * constructor [can be null, in which case null is returned].
+     *
+     * @return looked-up error message or the error code if it could not be
+     * looked up [null is returned on null 'code' input only].
+     *
+     * This method does not throw.
+     *
+     * @see AbstractException#getMessage
+     * @see AbstractRuntimeException#getMessage
+     */
+    static String getMessage (final Class namespace, final String code)
+    {
+        if (code == null) return null;
+        
+        try
+        {                      
+            if (code.length () > 0)
+            {
+                // look the code up in the resource bundle:
+                final String msg = lookup (namespace, code);     
+                if (msg == null)
+                {
+                    // if code lookup failed, return 'code' as is:
+                    return code;
+                }
+                else
+                {
+                    return EMBED_ERROR_CODE ? "[" + code + "] " + msg : msg;
+                }
+           }
+           else
+           {
+               return "";
+           }
+        }
+        catch (Throwable t)
+        {
+            // this method must never fail: default to returning the input
+            // verbatim on all unexpected problems
+            return code;
+        }
+    }
+    
+    /**
+     * Provides support for lookup of exception error codes from {@link AbstractException}
+     * and {@link AbstractRuntimeException} and their subclasses.
+     *
+     * @param namespace the Class object acting as the key to the namespace from
+     * which to retrieve the description for 'code' [can be null, in which case
+     * only the root namespace is used for lookup]
+     *
+     * @param code the message string value that was passed into exception
+     * constructor [can be null, in which case null is returned].
+     *
+     * @param arguments java.text.MessageFormat-style parameters to be substituted
+     * into the error message once it is looked up. 
+     *
+     * @return looked-up error message or the error code if it could not be
+     * looked up [null is returned on null 'code' input only].
+     *
+     * This method does not throw.
+     *
+     * @see AbstractException#getMessage
+     * @see AbstractRuntimeException#getMessage
+     */
+    static String getMessage (final Class namespace, final String code, final Object [] arguments)
+    {
+        if (code == null) return null;
+        final String pattern = getMessage (namespace, code);
+
+        // assertion: pattern != null
+        
+        if ((arguments == null) || (arguments.length == 0))
+        {
+            return pattern;
+        }
+        else
+        {
+            try
+            {
+                return MessageFormat.format (pattern, arguments);
+            }
+            catch (Throwable t)
+            {
+                // this method cannot fail: default to returning the input
+                // verbatim on all unexpected problems:
+                
+                final StringBuffer msg = new StringBuffer (code + EOL);
+                
+                for (int a = 0; a < arguments.length; a ++)
+                {
+                    msg.append ("\t{" + a + "} = [");
+                    final Object arg = arguments [a];
+                    try
+                    {
+                        msg.append (arg.toString ());
+                    }
+                    catch (Throwable e) // guard against bad toString() overrides
+                    {
+                        if (arg != null)
+                            msg.append (arg.getClass ().getName ());
+                        else
+                            msg.append ("null");
+                    }
+                    msg.append ("]");
+                    msg.append (EOL);
+                }
+                
+                return msg.toString ();
+            }
+        }
+    }
+    
+    // private: ...............................................................
+
+
+    private ExceptionCommon () {} // prevent subclassing
+        
+    /**
+     * Internal property lookup method. It implements the lookup scheme described
+     * in {@link #addExceptionResource}.
+     * 
+     * @return property value corresponding to 'propertyName' [null if lookup fails]
+     */
+    private static String lookup (Class namespace, final String propertyName)
+    {
+        if (propertyName == null) return null;
+        
+        // note: the following does not guard against exceptions that do not subclass
+        // our base classes [done elsewhere], however it will not crash either
+        
+        // check extension bundles:
+        if (namespace != null)
+        {
+            ResourceBundle rb;
+            while (namespace != ABSTRACT_EXCEPTION && namespace != ABSTACT_RUNTIME_EXCEPTION
+                   && namespace != THROWABLE && namespace != null)
+            {
+                synchronized (s_exceptionCodeMap)
+                {
+                    rb = (ResourceBundle) s_exceptionCodeMap.get (namespace);
+                    if (rb == null)
+                    {
+                        // check if there is a default bundle to be loaded for this namespace: 
+                        if ((rb = addExceptionResource (namespace, "exceptions")) == null)
+                        {
+                            // add an immutable empty bundle to avoid this check in the future:
+                            s_exceptionCodeMap.put (namespace, EMPTY_RESOURCE_BUNDLE);
+                        }
+                    }
+                }
+                
+                if (rb != null)
+                {
+                    String propertyValue = null;
+                    try
+                    {
+                        propertyValue = rb.getString (propertyName);
+                    }
+                    catch (Throwable ignore) {}
+                    if (propertyValue != null) return propertyValue;
+                }
+                
+                // walk the inheritance chain for 'namespace':
+                namespace = namespace.getSuperclass ();
+            }
+        }
+        
+        // if everything fails, check the root bundle:
+        if (ROOT_RESOURCE_BUNDLE != null)
+        {
+            String propertyValue = null;
+            try
+            {
+                propertyValue = ROOT_RESOURCE_BUNDLE.getString (propertyName);
+            }
+            catch (Throwable ignore) {}
+            if (propertyValue != null) return propertyValue;
+        }
+        
+        return null;
+    }
+    
+    private static String getNameInNamespace (final Class namespace, final String name)
+    {
+        if (namespace == null) return name;
+        
+        final String namespaceName = namespace.getName ();
+        final int lastDot = namespaceName.lastIndexOf ('.');
+        
+        if (lastDot <= 0)
+            return name;
+        else
+            return namespaceName.substring (0, lastDot + 1)  + name;
+    }
+    
+
+    // changes this to 'false' to eliminate repetition of error codes in
+    // the output of getMessage(): 
+    private static final boolean EMBED_ERROR_CODE = true;
+
+    // the name of the 'root' message resource bundle, derived as
+    // [this package name + ".exceptions"]:
+    private static final String ROOT_RESOURCE_BUNDLE_NAME;      // set in <clinit>
+    
+    // the root resource bundle; always checked if all other lookups fail:    
+    private static final ResourceBundle ROOT_RESOURCE_BUNDLE;   // set in <clinit>
+    
+    // static cache of all loaded resource bundles, populated via addExceptionResource():
+    private static final Map /* Class -> ResourceBundle */ s_exceptionCodeMap = new HashMap ();
+    
+    // misc constants:
+    
+    private static final String NESTED_THROWABLE_HEADER = "[NESTED EXCEPTION]:";
+    private static final Class THROWABLE                    = Throwable.class;
+    private static final Class ABSTRACT_EXCEPTION           = AbstractException.class;
+    private static final Class ABSTACT_RUNTIME_EXCEPTION    = AbstractRuntimeException.class;
+    /*private*/ static final Enumeration EMPTY_ENUMERATION  = Collections.enumeration (Collections.EMPTY_SET);    
+    private static final ResourceBundle EMPTY_RESOURCE_BUNDLE = new ResourceBundle ()
+    {
+        public Object handleGetObject (final String key)
+        {
+            return null;
+        }
+        
+        public Enumeration getKeys ()
+        {
+            return EMPTY_ENUMERATION;
+        }
+    };
+    
+    // end-of-line terminator for the current platform:
+    private static final String EOL = System.getProperty ("line.separator", "\n");
+    
+    
+    static
+    {
+        // set the name of ROOT_RESOURCE_BUNDLE_NAME:
+        ROOT_RESOURCE_BUNDLE_NAME = getNameInNamespace (ExceptionCommon.class, "exceptions");
+                
+        // set the root resource bundle:
+        ResourceBundle temprb = null;
+        try
+        {
+            temprb = ResourceBundle.getBundle (ROOT_RESOURCE_BUNDLE_NAME);
+        }
+        catch (Throwable ignore)
+        {
+            // if the exception codes rb is absent, we are still in a supported configuration
+        }
+        ROOT_RESOURCE_BUNDLE = temprb;        
+    }
+    
+} // end of class
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/util/exception/Exceptions.java b/core/java12/com/vladium/util/exception/Exceptions.java
new file mode 100644
index 0000000..1ee691a
--- /dev/null
+++ b/core/java12/com/vladium/util/exception/Exceptions.java
@@ -0,0 +1,47 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: Exceptions.java,v 1.1.1.1 2004/05/09 16:57:58 vlad_r Exp $
+ */
+package com.vladium.util.exception;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2002
+ */
+public
+abstract class Exceptions
+{
+    // public: ................................................................
+    
+    public static boolean unexpectedFailure (final Throwable t, final Class [] expected)
+    {
+        if (t == null) return false;
+        if (expected == null) return true;
+        
+        final Class reClass = t.getClass ();
+        
+        for (int e = 0; e < expected.length; ++ e)
+        {
+            if (expected [e] == null) continue;
+            if (expected [e].isAssignableFrom (reClass))
+                return false;
+        }
+        
+        return true;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private Exceptions () {} // this class is not extendible
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/com/vladium/util/exception/ICodedException.java b/core/java12/com/vladium/util/exception/ICodedException.java
new file mode 100644
index 0000000..0121226
--- /dev/null
+++ b/core/java12/com/vladium/util/exception/ICodedException.java
@@ -0,0 +1,42 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ICodedException.java,v 1.1.1.1 2004/05/09 16:57:58 vlad_r Exp $
+ */
+package com.vladium.util.exception;
+ 
+// ----------------------------------------------------------------------------
+/**
+ * TODO: javadoc
+ * 
+ * This interface is implemented by {@link AbstractException} and
+ * {@link AbstractRuntimeException} to provide a common interface
+ * for accessing error codes.<P>
+ * 
+ * An error code is a compact string representing the nature of exception
+ * in a programmatic locale-independent way. It can be used as a key that maps
+ * to a human-readable error message in a resource bundle. For details, see
+ * the exception classes mentioned above.
+ * 
+ * @author Vlad Roubtsov, (C) 2002
+ */
+public
+interface ICodedException
+{
+    // public: ................................................................
+    
+    /**
+     * Returns the String that was passed as 'message' argument to an exception
+     * constructor. For a coded exception this will be the compact error code
+     * [and different from the result of <code>getMessage()</code>], otherwise
+     * this will be traditional error message.
+     *
+     * @return message code string [can be null]
+     */
+    String getErrorCode ();
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/com/vladium/util/exception/IThrowableWrapper.java b/core/java12/com/vladium/util/exception/IThrowableWrapper.java
new file mode 100644
index 0000000..66174c0
--- /dev/null
+++ b/core/java12/com/vladium/util/exception/IThrowableWrapper.java
@@ -0,0 +1,57 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IThrowableWrapper.java,v 1.1.1.1 2004/05/09 16:57:58 vlad_r Exp $
+ */
+package com.vladium.util.exception;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+// ----------------------------------------------------------------------------
+/**
+ * TODO: javadoc
+ * 
+ * Any exception that wraps around another exception and wishes to be fully 
+ * inspectable by {@link ExceptionCommon} should implement this interface.
+ * Note that JDK 1.4+ obsoletes the need for an explicit interface like this,
+ * although the implementation in {@link ExceptionCommon} is upwards compatible
+ * with it.
+ * 
+ * @author Vlad Roubtsov, (C) 2002
+ */
+interface IThrowableWrapper
+{
+    // public: ................................................................
+
+    /**
+     * Gets the Throwable being wrapped. This method signature is the same as
+     * Throwable.getCause() in J2SE 1.4.
+     * 
+     * @return Throwable being wrapped by this object [can be null].
+     */
+    Throwable getCause ();
+     
+    /**
+     * Every exception hierarchy implementing this interface must ensure that
+     * this method delegates to super.printStackTrace(pw) where 'super' is the
+     * first superclass not implementing IThrowableWrapper. This is used by
+     * {@link ExceptionCommon} to avoid infinite
+     * recursion and is not meant to be called by other classes.
+     */
+    void __printStackTrace (PrintWriter pw);
+
+    /**
+     * Every exception hierarchy implementing this interface must ensure that
+     * this method delegates to super.printStackTrace(ps) where 'super' is the
+     * first superclass not implementing IThrowableWrapper. This is used by
+     * {@link ExceptionCommon} to avoid infinite
+     * recursion and is not meant to be called by other classes.
+     */
+    void __printStackTrace (PrintStream ps);
+
+} // end of interface
+// ----------------------------------------------------------------------------
diff --git a/core/java12/emma.java b/core/java12/emma.java
new file mode 100644
index 0000000..dc64cf5
--- /dev/null
+++ b/core/java12/emma.java
@@ -0,0 +1,66 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: emma.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $
+ */
+import com.vladium.emma.IAppConstants;
+import com.vladium.emma.Command;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class emma
+{
+    // public: ................................................................
+    
+    // TODO: set m_out consistently with LoggerInit    
+    
+    public static void main (final String [] args)
+        throws EMMARuntimeException
+    {
+        // TODO: proper usage, arg validation, etc
+        
+        if ((args.length == 0) || args [0].startsWith ("-h"))
+        {
+            System.out.println (USAGE);
+            return;
+        }
+        
+        final String commandName = args [0];
+        final String [] commandArgs = new String [args.length - 1];
+        System.arraycopy (args, 1, commandArgs, 0, commandArgs.length);
+        
+        final Command command = Command.create (commandName, "emma ".concat (commandName), commandArgs);
+        command.run ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+    
+    
+    private static final String EOL = System.getProperty ("line.separator", "\n"); 
+    
+    private static final String USAGE =
+    "emma usage: emma <command> [command options]," + EOL +
+    "  where <command> is one of:" + EOL +
+    EOL +
+    "   run     application runner {same as 'emmarun' tool};" + EOL +
+    "   instr   offline instrumentation processor;" + EOL +
+    "   report  offline report generator;" + EOL +
+    "   merge   offline data file merge processor." + EOL +
+    EOL +
+    "  {use '<command> -h' to see usage help for a given command}" + EOL +
+    EOL +
+    IAppConstants.APP_USAGE_BUILD_ID;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java12/emmarun.java b/core/java12/emmarun.java
new file mode 100644
index 0000000..cc5f76d
--- /dev/null
+++ b/core/java12/emmarun.java
@@ -0,0 +1,37 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: emmarun.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $
+ */
+import com.vladium.emma.Command;
+import com.vladium.emma.EMMARuntimeException;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+final class emmarun
+{
+    // public: ................................................................
+    
+    // TODO: set m_out consistently with LoggerInit    
+    
+    public static void main (final String [] args)
+        throws EMMARuntimeException
+    {
+        final Command command = Command.create ("run", emmarun.class.getName (), args);
+        command.run ();
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java13/com/vladium/util/exit/ExitHookManager.java b/core/java13/com/vladium/util/exit/ExitHookManager.java
new file mode 100644
index 0000000..c119268
--- /dev/null
+++ b/core/java13/com/vladium/util/exit/ExitHookManager.java
@@ -0,0 +1,252 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ExitHookManager.java,v 1.1.1.1 2004/05/09 16:57:58 vlad_r Exp $
+ */
+package com.vladium.util.exit;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import com.vladium.util.IJREVersion;
+import com.vladium.util.Property;
+import com.vladium.emma.IAppConstants;
+
+// ----------------------------------------------------------------------------
+/**
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+abstract class ExitHookManager implements IJREVersion
+{
+    // public: ................................................................
+    
+    // TOTO: handle thread groups as well?
+    
+    public abstract boolean addExitHook (Runnable runnable);
+    public abstract boolean removeExitHook (Runnable runnable);
+    
+    public static synchronized ExitHookManager getSingleton ()
+    {
+        if (s_singleton == null)
+        {
+            if (JRE_1_3_PLUS)
+            {
+                s_singleton = new JRE13ExitHookManager ();
+            }
+            else if (JRE_SUN_SIGNAL_COMPATIBLE)
+            {
+                s_singleton = new SunJREExitHookManager ();
+            }
+            else
+            {
+                throw new UnsupportedOperationException ("no shutdown hook manager available [JVM: " + Property.getSystemFingerprint () + "]");
+            }
+        }
+        
+        return s_singleton;
+    }
+    
+    // protected: .............................................................
+    
+    
+    protected ExitHookManager () {}
+
+    // package: ...............................................................
+
+    // private: ...............................................................
+    
+    
+    private static final class JRE13ExitHookManager extends ExitHookManager
+    {
+        public synchronized boolean addExitHook (final Runnable runnable)
+        {
+            if ((runnable != null) && ! m_exitThreadMap.containsKey (runnable))
+            {
+                final Thread exitThread = new Thread (runnable, IAppConstants.APP_NAME + " shutdown handler thread");
+                
+                try
+                {
+                    Runtime.getRuntime ().addShutdownHook (exitThread);
+                    m_exitThreadMap.put (runnable, exitThread); // TODO: use identity here
+                    
+                    return true;
+                }
+                catch (Exception e)
+                {
+                    System.out.println ("exception caught while adding a shutdown hook:");
+                    e.printStackTrace (System.out);
+                }
+            }
+            
+            return false;
+        }
+        
+        public synchronized boolean removeExitHook (final Runnable runnable)
+        {
+            if (runnable != null)
+            {
+                final Thread exitThread = (Thread) m_exitThreadMap.get (runnable);  // TODO: use identity here
+                
+                if (exitThread != null)
+                {
+                    try
+                    {
+                        Runtime.getRuntime ().removeShutdownHook (exitThread);
+                        m_exitThreadMap.remove (runnable);
+                        
+                        return true;
+                    }
+                    catch (Exception e)
+                    {
+                        System.out.println ("exception caught while removing a shutdown hook:");
+                        e.printStackTrace (System.out);
+                    }
+                }
+            }
+            
+            return false;
+        }
+        
+        JRE13ExitHookManager ()
+        {
+            m_exitThreadMap = new HashMap ();
+        }
+        
+        
+        private final Map /* Runnable->Thread */ m_exitThreadMap;
+        
+    } // end of nested class
+    
+    
+    private static final class SunJREExitHookManager extends ExitHookManager
+    {
+        public synchronized boolean addExitHook (final Runnable runnable)
+        {
+            if ((runnable != null) && ! m_signalHandlerMap.containsKey (runnable))
+            {
+                final INTSignalHandler handler = new INTSignalHandler (runnable);
+                
+                try
+                {
+                    handler.register ();
+                    m_signalHandlerMap.put (runnable, handler); // TODO: use identity here
+                    
+                    return true;
+                }
+                catch (Throwable t)
+                {
+                    System.out.println ("exception caught while adding a shutdown hook:");
+                    t.printStackTrace (System.out);
+                }
+            }
+            
+            return false;
+        }
+        
+        public synchronized boolean removeExitHook (final Runnable runnable)
+        {
+            if (runnable != null)
+            {
+                final INTSignalHandler handler = (INTSignalHandler) m_signalHandlerMap.get (runnable);  // TODO: use identity here
+                if (handler != null)
+                {
+                    try
+                    {
+                        handler.unregister ();
+                        m_signalHandlerMap.remove (runnable);
+                        
+                        return true;
+                    }
+                    catch (Exception e)
+                    {
+                        System.out.println ("exception caught while removing a shutdown hook:");
+                        e.printStackTrace (System.out);
+                    }
+                }
+            }
+            
+            return false;
+        }
+        
+        SunJREExitHookManager ()
+        {
+            m_signalHandlerMap = new HashMap ();
+        }
+        
+        
+        private final Map /* Runnable->INTSignalHandler */ m_signalHandlerMap;
+        
+    } // end of nested class
+    
+    
+    private static final class INTSignalHandler implements SignalHandler
+    {
+        public synchronized void handle (final Signal signal)
+        {
+            if (m_runnable != null)
+            {
+                try
+                {
+                    m_runnable.run ();
+                }
+                catch (Throwable ignore) {}
+            }
+            m_runnable = null;
+            
+            if ((m_previous != null) && (m_previous != SIG_DFL) && (m_previous != SIG_IGN))
+            {
+                try
+                {
+                    // this does not work:
+                    //Signal.handle (signal, m_previous);
+                    //Signal.raise (signal);
+                    
+                    m_previous.handle (signal);
+                }
+                catch (Throwable ignore) {}
+            }
+            else
+            {
+                System.exit (0);
+            }
+        }
+        
+        INTSignalHandler (final Runnable runnable)
+        {
+            m_runnable = runnable;
+        }
+       
+        synchronized void register ()
+        {
+            m_previous = Signal.handle (new Signal ("INT"), this);
+        }
+        
+        synchronized void unregister ()
+        {
+//            if (m_previous != null)
+//            {
+//                Signal.handle (new Signal ("INT"), m_previous);
+//                m_previous = null;
+//            }
+
+            m_runnable = null;
+        }
+
+
+        private Runnable m_runnable;
+        private SignalHandler m_previous;
+        
+    } // end of nested class
+    
+    
+    private static ExitHookManager s_singleton;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/core/java14/com/vladium/util/IJREVersion.java b/core/java14/com/vladium/util/IJREVersion.java
new file mode 100644
index 0000000..866c3fe
--- /dev/null
+++ b/core/java14/com/vladium/util/IJREVersion.java
@@ -0,0 +1,101 @@
+/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: IJREVersion.java,v 1.1.1.1.2.1 2004/07/10 03:34:52 vlad_r Exp $
+ */
+package com.vladium.util;
+
+// ----------------------------------------------------------------------------
+/**
+ * A constant collection to detect the current JRE version. Note that this code
+ * is more sophisticated then a mere check of "java.version" or "java.class.version"
+ * and similar system properties because some JVMs allow overriding those
+ * by the user (e.g., from the command line). This implementation relies on
+ * the core classes only and tries to minimize the number of security-sensitive
+ * methods it uses.<P>
+ * 
+ * This interface is supported in Java 1.1+ and should be compiled with class
+ * version stamp 45.3 (-target 1.1).
+ * 
+ * @author Vlad Roubtsov, (C) 2003
+ */
+public
+interface IJREVersion
+{
+    // public: ................................................................
+
+    /** 'true' iff the current runtime version is 1.2 or later */
+    boolean JRE_1_2_PLUS = _JREVersion._JRE_1_2_PLUS; // static final but not inlinable
+    /** 'true' iff the current runtime version is 1.3 or later */
+    boolean JRE_1_3_PLUS = _JREVersion._JRE_1_3_PLUS; // static final but not inlinable
+    /** 'true' iff the current runtime version is 1.4 or later */
+    boolean JRE_1_4_PLUS = _JREVersion._JRE_1_4_PLUS; // static final but not inlinable
+    
+    // supporting Java 1.5 is trivial...
+    
+    boolean JRE_SUN_SIGNAL_COMPATIBLE = _JREVersion._JRE_SUN_SIGNAL_COMPATIBLE;
+    
+    /*
+     * Use a dummy nested class to fake a static initializer for the outer
+     * interface (I want IJREVersion as an interface and not a class so that
+     * all JRE_XXX constants could be imported via "implements").
+     */
+    abstract class _JREVersion
+    {
+        static final boolean _JRE_1_2_PLUS; // set in <clinit>
+        static final boolean _JRE_1_3_PLUS; // set in <clinit>    
+        static final boolean _JRE_1_4_PLUS; // set in <clinit>
+        
+        static final boolean _JRE_SUN_SIGNAL_COMPATIBLE; // set in <clinit>
+        
+        private _JREVersion () {} // prevent subclassing
+    
+        static
+        {
+            _JRE_1_2_PLUS = ((SecurityManager.class.getModifiers () & 0x0400) == 0);
+
+            boolean temp = false;            
+            if (_JRE_1_2_PLUS)
+            {
+                try
+                {
+                    StrictMath.abs (1.0);
+                    temp = true;
+                }
+                catch (Error ignore) {}
+            }
+            _JRE_1_3_PLUS = temp;
+            
+            if (temp)
+            {
+                temp = false;
+                try
+                {
+                    " ".subSequence (0, 0);
+                    temp = true;
+                }
+                catch (NoSuchMethodError ignore) {}
+            }
+            _JRE_1_4_PLUS = temp;
+            
+            temp = false;
+            try
+            {
+                Class.forName ("sun.misc.Signal");
+                Class.forName ("sun.misc.SignalHandler");
+                
+                temp = true;
+            }
+            catch (Throwable ignore) {}
+            
+            _JRE_SUN_SIGNAL_COMPATIBLE = temp;
+        }
+
+    } // end of nested class
+
+} // end of interface
+// ----------------------------------------------------------------------------
+
diff --git a/core/res/com/vladium/emma/data/merge_usage.res b/core/res/com/vladium/emma/data/merge_usage.res
new file mode 100644
index 0000000..2ebf4d6
--- /dev/null
+++ b/core/res/com/vladium/emma/data/merge_usage.res
@@ -0,0 +1,52 @@
+
+'in', 'input':
+	required, mergeable, values: 1,
+	'<list of files>',
+	"list of meta/coverage data files";
+
+'out', 'outfile':
+	optional, values: 1,
+	'<file>',
+	"merged data output file (defaults to 'coverage.es')";
+
+'p', 'props', 'properties':
+	optional, values: 1,
+	'<properties file>',
+	"properties override file";
+
+'D':
+	optional, mergeable, detailedonly, pattern, values: 1,
+	'<value>',
+	"generic property override";
+
+'exit':
+	optional, detailedonly, values: 0,
+	"use System.exit() on termination";
+
+'verbose':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'quiet', 'debug'},
+	"verbose output operation";
+
+'quiet':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'verbose', 'debug'},
+	"quiet operation (ignore all but warnings and severe errors)";
+
+'silent':
+	optional, detailedonly, values: 0,
+	excludes {'quiet', 'verbose', 'debug'},
+	"extra-quiet operation (ignore all but severe errors)";
+
+'debug', 'loglevel': 
+	optional, detailedonly, values: ?,
+	'[<debug trace level>]',
+	excludes {'verbose', 'quiet', 'silent'},
+	"debug tracing level";
+
+'debugcls':
+	optional, detailedonly, values: 1,
+	'<debug trace class mask>',
+	"class mask for debug tracing";
+
+
diff --git a/core/res/com/vladium/emma/exceptions.properties b/core/res/com/vladium/emma/exceptions.properties
new file mode 100644
index 0000000..217ad78
--- /dev/null
+++ b/core/res/com/vladium/emma/exceptions.properties
@@ -0,0 +1,63 @@
+# -----------------------------------------------------------------------------
+# this is loaded as a resource bundle by com.vladium.emma.* exceptions
+# -----------------------------------------------------------------------------
+
+UNEXPECTED_FAILURE: \
+unexpected failure {0}, please submit a bug report to: ''{1}''
+
+INVALID_PARAMETER_VALUE: \
+value ''{1}'' is not valid for parameter ''{0}''
+
+REQUIRED_PARAMETER_MISSING: \
+required parameter [{0}] is not specified 
+
+INVALID_COLUMN_NAME: \
+invalid report column name [{0}]
+
+SECURITY_RESTRICTION: \
+current security settings are too restrictive for {0} to operate
+
+MAIN_CLASS_BAD_DELEGATION: \
+the application class [{1}] that you are trying to launch was not loaded \
+by {0} instrumenting classloader, possibly because is was not listed in \
+the '-cp' option: {0} will not be able to collect any coverage data. The \
+actual classloader was: {2}
+
+MAIN_CLASS_NOT_FOUND: \
+application class [{0}] could not be loaded
+
+MAIN_CLASS_LOAD_FAILURE: \
+application class [{0}] could not be loaded and initialized, most likely due \
+to static initializer failure: {1}
+
+MAIN_METHOD_NOT_FOUND: \
+application class [{0}] does not have a runnable public main() method
+
+MAIN_METHOD_FAILURE: \
+{0}.main() method failure: {1}
+
+REPORT_GEN_FAILURE: \
+failed to generate report
+
+REPORT_IO_FAILURE: \
+exception occurred while writing report file(s):
+
+CLASS_STAMP_MISMATCH: \
+runtime version of class [{0}] in the coverage data is not consistent \
+with the version of this class in the metadata, possibly because stale \
+metadata is being used for report generation.
+
+OUT_MKDIR_FAILURE: \
+output directory [{0}] could not be created
+
+INSTR_IO_FAILURE: \
+exception occurred while writing instrumented file(s):
+
+OUT_IO_FAILURE: \
+exception occurred while writing output file [{0}]:
+
+ARGS_IO_FAILURE: \
+exception while processing settings:
+
+# -----------------------------------------------------------------------------
+# end of file
diff --git a/core/res/com/vladium/emma/instr/instr_usage.res b/core/res/com/vladium/emma/instr/instr_usage.res
new file mode 100644
index 0000000..56a6699
--- /dev/null
+++ b/core/res/com/vladium/emma/instr/instr_usage.res
@@ -0,0 +1,72 @@
+
+'ip', 'cp', 'instrpath':
+	required, mergeable, values: 1,
+	'<class directories and zip/jar files>',
+	"instrumentation path";
+
+'d', 'dir', 'outdir':
+	optional, values: 1,
+	'<directory>',
+	"instrumentation output directory (required for non-overwrite output modes)";
+
+'out', 'outfile':
+	optional, values: 1,
+	'<file>',
+	"metadata output file (defaults to 'coverage.em')";
+
+'merge':
+	optional, values: 1,
+    '(y[es]|n[o])',
+	"merge metadata into output file, if it exists";
+
+'m', 'outmode':
+	optional, values: 1,
+	'(copy|overwrite|fullcopy)',
+	"output mode (defaults to 'copy')";
+
+'ix', 'filter':
+	optional, mergeable, values: 1,
+	'<class name wildcard patterns>',
+	"coverage inclusion/exclusion patterns {?,*}";
+
+'p', 'props', 'properties':
+	optional, values: 1,
+	'<properties file>',
+	"properties override file";
+
+'D':
+	optional, mergeable, detailedonly, pattern, values: 1,
+	'<value>',
+	"generic property override";
+
+'exit':
+	optional, detailedonly, values: 0,
+	"use System.exit() on termination";
+
+'verbose':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'quiet', 'debug'},
+	"verbose output operation";
+
+'quiet':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'verbose', 'debug'},
+	"quiet operation (ignore all but warnings and severe errors)";
+
+'silent':
+	optional, detailedonly, values: 0,
+	excludes {'quiet', 'verbose', 'debug'},
+	"extra-quiet operation (ignore all but severe errors)";
+
+'debug', 'loglevel': 
+	optional, detailedonly, values: ?,
+	'[<debug trace level>]',
+	excludes {'verbose', 'quiet', 'silent'},
+	"debug tracing level";
+
+'debugcls':
+	optional, detailedonly, values: 1,
+	'<debug trace class mask>',
+	"class mask for debug tracing";
+
+
diff --git a/core/res/com/vladium/emma/report/report_usage.res b/core/res/com/vladium/emma/report/report_usage.res
new file mode 100644
index 0000000..f91c241
--- /dev/null
+++ b/core/res/com/vladium/emma/report/report_usage.res
@@ -0,0 +1,57 @@
+
+'in', 'input':
+	required, mergeable, values: 1,
+	'<list of files>',
+	"list of meta/coverage data files";
+
+'r', 'report':
+	required, mergeable, values: 1,
+	'<list of {txt|html|xml}>',
+	"coverage report type list";
+
+'sp', 'sourcepath':
+	optional, mergeable, values: 1,
+	'<list of source directories>',
+	"Java source path for generating reports";
+
+'p', 'props', 'properties':
+	optional, values: 1,
+	'<properties file>',
+	"properties override file";
+
+'D':
+	optional, mergeable, detailedonly, pattern, values: 1,
+	'<value>',
+	"generic property override";
+
+'exit':
+	optional, detailedonly, values: 0,
+	"use System.exit() on termination";
+
+'verbose':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'quiet', 'debug'},
+	"verbose output operation";
+
+'quiet':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'verbose', 'debug'},
+	"quiet operation (ignore all but warnings and severe errors)";
+
+'silent':
+	optional, detailedonly, values: 0,
+	excludes {'quiet', 'verbose', 'debug'},
+	"extra-quiet operation (ignore all but severe errors)";
+
+'debug', 'loglevel': 
+	optional, detailedonly, values: ?,
+	'[<debug trace level>]',
+	excludes {'verbose', 'quiet', 'silent'},
+	"debug tracing level";
+
+'debugcls':
+	optional, detailedonly, values: 1,
+	'<debug trace class mask>',
+	"class mask for debug tracing";
+
+
diff --git a/core/res/com/vladium/emma/run_usage.res b/core/res/com/vladium/emma/run_usage.res
new file mode 100644
index 0000000..42d6f7c
--- /dev/null
+++ b/core/res/com/vladium/emma/run_usage.res
@@ -0,0 +1,88 @@
+
+'cp', 'classpath':
+	optional, mergeable, values: 1,
+	'<list of directories and zip/jar files>',
+    excludes { 'jar' },
+	"load/coverage classpath";
+
+'jar':
+	optional, values: 0,
+    excludes { 'cp' },
+	"run an executable jar file";
+
+'f', 'fullmetadata':
+	optional, values: 0,
+	"consider the entire classpath for coverage {including classes that are never loaded}";
+
+'ix', 'filter':
+	optional, mergeable, values: 1,
+	'<class name wildcard patterns>',
+	"coverage inclusion/exclusion patterns {?,*}";
+
+'r', 'report':
+	optional, mergeable, values: 1,
+	'<list of {txt|html|xml}>',
+	"coverage report type list";
+
+'sp', 'sourcepath':
+	optional, mergeable, values: 1,
+	'<list of source directories>',
+	"Java source path for generating reports";
+
+'raw', 'sessiondata':
+	optional, values: 0,
+	"dump raw session data file";
+
+'out', 'outfile':
+	optional, values: 1,
+	'<file>',
+    requires { 'raw' },
+	"raw session data output file (defaults to 'coverage.es')";
+
+'merge':
+	optional, values: 1,
+    '<y[es]|n[o]>',
+    requires { 'raw' },
+	"merge session data into output file, if it exists";
+
+'p', 'props', 'properties':
+	optional, values: 1,
+	'<properties file>',
+	"properties override file";
+
+'D':
+	optional, mergeable, detailedonly, pattern, values: 1,
+	'<value>',
+	"generic property override";
+
+'exit':
+	optional, detailedonly, values: 0,
+	"use System.exit() on termination";
+
+'verbose':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'quiet', 'debug'},
+	"verbose output operation";
+
+'quiet':
+	optional, detailedonly, values: 0,
+	excludes {'silent', 'verbose', 'debug'},
+	"quiet operation (ignore all but warnings and severe errors)";
+
+'silent':
+	optional, detailedonly, values: 0,
+	excludes {'quiet', 'verbose', 'debug'},
+	"extra-quiet operation (ignore all but severe errors)";
+
+'debug', 'loglevel': 
+	optional, detailedonly, values: ?,
+	'[<debug trace level>]',
+	excludes {'verbose', 'quiet', 'silent'},
+	"debug tracing level";
+
+'debugcls':
+	optional, detailedonly, values: 1,
+	'<debug trace class mask>',
+	"class mask for debug tracing";
+
+
diff --git a/core/res/emma_ant.properties b/core/res/emma_ant.properties
new file mode 100644
index 0000000..7fca4c0
--- /dev/null
+++ b/core/res/emma_ant.properties
@@ -0,0 +1,7 @@
+# -------------------------------------------------------------
+
+emma:		com.vladium.emma.emmaTask
+emmajava:	com.vladium.emma.emmajavaTask
+
+# -------------------------------------------------------------
+# end of file
diff --git a/core/res/emma_default.properties b/core/res/emma_default.properties
new file mode 100644
index 0000000..ec4fecb
--- /dev/null
+++ b/core/res/emma_default.properties
@@ -0,0 +1,91 @@
+# -------------------------------------------------------------
+# *************** DO NOT EDIT THIS FILE ***********************
+#
+# for user-editable property overrides use one of these options:
+#
+# (1) option-specific command line overrides, e.g.
+#     -Dreport.txt.out.file=coverage.txt
+#
+# (2) '-p <options file>' command line option,
+#
+# (3) 'emma.properties' resource placed somewhere in the classpath
+#     (e.g., in <jre dir>\classes directory -- note that it does
+#     not exist by default),
+#
+# (4) '-Demma.*' JVM options, e.g.
+#     -Demma.report.txt.out.file=coverage.txt
+#
+# (5) 'emma.properties' JVM option pointing to a properties file
+#     -Demma.properties=./myproperties.txt
+# -------------------------------------------------------------
+
+# -------------------------------------------------------------
+# logging properties:
+
+verbosity.level:	info
+
+# classloading properties:
+
+#clsload.forced_delegation_filter:
+#clsload.through_delegation_filter: -*
+
+# -------------------------------------------------------------
+
+# instrumentation properties:
+
+instr.exclude_synthetic_methods:	true
+instr.exclude_bridge_methods:		true
+instr.do_suid_compensation:		true
+
+# -------------------------------------------------------------
+
+# apprunner session data output properties:
+
+session.out.file:	coverage.es
+session.out.merge:	true
+
+# -------------------------------------------------------------
+
+# runtime coverage data output properties:
+
+coverage.out.file:	coverage.ec
+coverage.out.merge:	true
+
+# -------------------------------------------------------------
+
+# instr metadata output properties:
+
+metadata.out.file:	coverage.em
+metadata.out.merge:	true
+
+# -------------------------------------------------------------
+
+# common report defaults:
+
+report.units:		instr
+report.depth:		method
+report.columns:		name,class,method,block,line
+report.sort:		+block,+name,+method,+class
+report.metrics:		method:70,block:80,line:80,class:100
+
+# -------------------------------------------------------------
+# txt report properties:
+
+report.txt.depth:	all
+report.txt.columns:	class,method,block,line,name
+report.txt.out.file:	coverage.txt
+
+# -------------------------------------------------------------
+# html report properties:
+
+#report.html.out.dir:	coverage
+report.html.out.file:	coverage/index.html
+report.html.out.encoding: ISO-8859-1
+
+# -------------------------------------------------------------
+# xml report properties:
+
+report.xml.out.file:	coverage.xml
+report.xml.out.encoding: UTF-8
+# -------------------------------------------------------------
+# end of file
diff --git a/cpl-v10.html b/cpl-v10.html
new file mode 100644
index 0000000..36aa208
--- /dev/null
+++ b/cpl-v10.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<HTML>
+<HEAD>
+<TITLE>Common Public License - v 1.0</TITLE>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</HEAD>
+
+<BODY BGCOLOR="#FFFFFF" VLINK="#800000">
+
+
+<P ALIGN="CENTER"><B>Common Public License - v 1.0</B>
+<P><B></B><FONT SIZE="3"></FONT>
+<P><FONT SIZE="3"></FONT><FONT SIZE="2">THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT").  ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.</FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"><B>1.  DEFINITIONS</B></FONT>
+<P><FONT SIZE="2">"Contribution" means:</FONT>
+
+<UL><FONT SIZE="2">a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and<BR CLEAR="LEFT">
+b) in the case of each subsequent Contributor:</FONT></UL>
+
+
+<UL><FONT SIZE="2">i)	 	changes to the Program, and</FONT></UL>
+
+
+<UL><FONT SIZE="2">ii)		additions to the Program;</FONT></UL>
+
+
+<UL><FONT SIZE="2">where such changes and/or additions to the Program originate from and are distributed by that particular Contributor.  </FONT><FONT SIZE="2">A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf.  </FONT><FONT SIZE="2">Contributions do not include additions to the Program which:  (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.  </FONT></UL>
+
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">"Contributor" means any person or entity that distributes the Program.</FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">"Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.  </FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2">"Program" means the Contributions distributed in accordance with this Agreement.</FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.</FONT>
+<P><FONT SIZE="2"><B></B></FONT>
+<P><FONT SIZE="2"><B>2.  GRANT OF RIGHTS</B></FONT>
+
+<UL><FONT SIZE="2"></FONT><FONT SIZE="2">a)	</FONT><FONT SIZE="2">Subject to the terms of this Agreement, each Contributor hereby grants</FONT><FONT SIZE="2"> Recipient a non-exclusive, worldwide, royalty-free copyright license to</FONT><FONT SIZE="2" COLOR="#FF0000"> </FONT><FONT SIZE="2">reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.</FONT></UL>
+
+
+<UL><FONT SIZE="2"></FONT></UL>
+
+
+<UL><FONT SIZE="2"></FONT><FONT SIZE="2">b) 	Subject to the terms of this Agreement, each Contributor hereby grants </FONT><FONT SIZE="2">Recipient a non-exclusive, worldwide,</FONT><FONT SIZE="2" COLOR="#008000"> </FONT><FONT SIZE="2">royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form.  This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents.  The patent license shall not apply to any other combinations which include the Contribution.  No hardware per se is licensed hereunder.   </FONT></UL>
+
+
+<UL><FONT SIZE="2"></FONT></UL>
+
+
+<UL><FONT SIZE="2">c)	Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity.  Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise.  As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any.  For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.</FONT></UL>
+
+
+<UL><FONT SIZE="2"></FONT></UL>
+
+
+<UL><FONT SIZE="2">d)	Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. </FONT></UL>
+
+
+<UL><FONT SIZE="2"></FONT></UL>
+
+<P><FONT SIZE="2"><B>3.  REQUIREMENTS</B></FONT>
+<P><FONT SIZE="2"><B></B>A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:</FONT>
+
+<UL><FONT SIZE="2">a)	it complies with the terms and conditions of this Agreement; and</FONT></UL>
+
+
+<UL><FONT SIZE="2">b)	its license agreement:</FONT></UL>
+
+
+<UL><FONT SIZE="2">i)	effectively disclaims</FONT><FONT SIZE="2"> on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; </FONT></UL>
+
+
+<UL><FONT SIZE="2">ii) 	effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; </FONT></UL>
+
+
+<UL><FONT SIZE="2">iii)</FONT><FONT SIZE="2">	states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and</FONT></UL>
+
+
+<UL><FONT SIZE="2">iv)	states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.</FONT><FONT SIZE="2" COLOR="#0000FF"> </FONT><FONT SIZE="2" COLOR="#FF0000"></FONT></UL>
+
+
+<UL><FONT SIZE="2" COLOR="#FF0000"></FONT><FONT SIZE="2"></FONT></UL>
+
+<P><FONT SIZE="2">When the Program is made available in source code form:</FONT>
+
+<UL><FONT SIZE="2">a)	it must be made available under this Agreement; and </FONT></UL>
+
+
+<UL><FONT SIZE="2">b)	a copy of this Agreement must be included with each copy of the Program.  </FONT></UL>
+
+<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT>
+<P><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT><FONT SIZE="2">Contributors may not remove or alter any copyright notices contained within the Program.  </FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.  </FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"><B>4.  COMMERCIAL DISTRIBUTION</B></FONT>
+<P><FONT SIZE="2">Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like.  While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors.   Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering.  The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement.  In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations.  The Indemnified Contributor may participate in any such claim at its own expense.</FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">For example, a Contributor might include the Program in a commercial product offering, Product X.  That Contributor is then a Commercial Contributor.  If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone.  Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.</FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"></FONT>
+<P><FONT SIZE="2" COLOR="#0000FF"></FONT><FONT SIZE="2"><B>5.  NO WARRANTY</B></FONT>
+<P><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is</FONT><FONT SIZE="2"> solely responsible for determining the appropriateness of using and distributing </FONT><FONT SIZE="2">the Program</FONT><FONT SIZE="2"> and assumes all risks associated with its exercise of rights under this Agreement</FONT><FONT SIZE="2">, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, </FONT><FONT SIZE="2">programs or equipment, and unavailability or interruption of operations</FONT><FONT SIZE="2">.  </FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2"><B>6.  DISCLAIMER OF LIABILITY</B></FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES </FONT><FONT SIZE="2">(INCLUDING WITHOUT LIMITATION LOST PROFITS),</FONT><FONT SIZE="2"> HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"><B>7.  GENERAL</B></FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2">If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.</FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed.  In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. </FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance.  If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable.  However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.  </FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2">Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted  and may only be modified in the following manner. The Agreement Steward reserves the right to </FONT><FONT SIZE="2">publish new versions (including revisions) of this Agreement from time to </FONT><FONT SIZE="2">time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward.   IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity.  </FONT><FONT SIZE="2">Each new version of the Agreement will be given a distinguishing version number.  The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new </FONT><FONT SIZE="2">version.  </FONT><FONT SIZE="2">Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, </FONT><FONT SIZE="2">by implication, estoppel or otherwise</FONT><FONT SIZE="2">.</FONT><FONT SIZE="2">  All rights in the Program not expressly granted under this Agreement are reserved.</FONT>
+<P><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2">This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose.  Each party waives its rights to a jury trial in any resulting litigation.</FONT>
+<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
+<P><FONT SIZE="2"></FONT>
+
+</BODY>
+
+</HTML>
\ No newline at end of file
diff --git a/dependencies.xml b/dependencies.xml
new file mode 100644
index 0000000..77ccad7
--- /dev/null
+++ b/dependencies.xml
@@ -0,0 +1,117 @@
+  <!-- ============================================================== -->
+
+  <patternset id="java.libs" includes="*.jar,*.zip" /> <!-- not recursing by design -->
+
+
+  <!-- ANT 1.4.x dependency: -->
+  <path id="boot.ant.14.classpath" >
+    <pathelement location="${build.target.ant.14.home}/lib/ant.jar" />
+  </path>
+
+  <!-- ANT 1.5.x dependency: -->
+  <path id="boot.ant.15.classpath" >
+    <pathelement location="${build.target.ant.15.home}/lib/ant.jar" />
+  </path>
+
+
+  <!-- J2SE 1.2 boot classpath: -->
+  <path id="boot.j2se.12.classpath" >
+    <fileset dir="${build.target.j2se.12.home}/lib/" >
+      <patternset refid="java.libs" />
+    </fileset>
+  </path>
+
+  <!-- J2SE 1.2 ext classpath: -->
+  <property name="ext.j2se.12.classpath"
+    value="${build.target.j2se.12.home}/lib/ext/"
+  />
+
+
+  <!-- J2SE 1.3 boot classpath: -->
+  <path id="boot.j2se.13.classpath" >
+    <fileset dir="${build.target.j2se.13.home}/lib/" >
+      <patternset refid="java.libs" />
+    </fileset>
+  </path>
+
+  <!-- J2SE 1.3 ext classpath: -->
+  <property name="ext.j2se.13.classpath"
+    value="${build.target.j2se.13.home}/lib/ext/"
+  />
+
+
+  <!-- J2SE 1.4 boot classpath: -->
+  <path id="boot.j2se.14.classpath" >
+    <fileset dir="${build.target.j2se.14.home}/lib/" >
+      <patternset refid="java.libs" />
+    </fileset>
+  </path>
+
+  <!-- J2SE 1.4 ext classpath: -->
+  <property name="ext.j2se.14.classpath"
+    value="${build.target.j2se.14.home}/lib/ext/"
+  />
+
+  <!-- ============================================================== -->
+  <!-- lib module: -->
+
+  <!-- 3rd party dependencies: -->
+  <path id="lib.external.classpath" >
+    <fileset dir="${external.lib.dir}" >
+      <patternset refid="java.libs" />
+    </fileset>
+  </path>
+
+  <!-- internal dependencies: -->
+  <path id="lib.internal.classpath" >
+    <fileset dir="${internal.lib.dir}" >
+      <patternset refid="java.libs" />
+    </fileset>
+  </path>
+
+  <!-- complete dependency classpath: -->
+  <path id="lib.classpath" >
+    <path refid="lib.internal.classpath" />
+    <path refid="lib.external.classpath" />
+  </path>
+
+  <!-- ============================================================== -->
+  <!-- core module: -->
+
+  <!-- compiled module classes + res + lib.classpath: -->
+  <path id="core.classpath" >
+    <pathelement location="${core.classes.out.dir}" />
+    <pathelement location="${core.res.dir}" />
+    <path refid="lib.classpath" />
+  </path>
+
+  <!-- ============================================================== -->
+  <!-- ant module: -->
+
+  <!-- compiled module classes + res + +ant classpath + core.classpath: -->
+  <path id="ant.14.classpath" >
+    <pathelement location="${ant.classes.out.dir}" />
+    <pathelement location="${ant.res.dir}" />
+    <path refid="core.classpath" />
+    <path refid="boot.ant.14.classpath" />
+  </path>
+
+  <!-- compiled module classes + res + +ant classpath + core.classpath: -->
+  <path id="ant.15.classpath" >
+    <pathelement location="${ant.classes.out.dir}" />
+    <pathelement location="${ant.res.dir}" />
+    <path refid="core.classpath" />
+    <path refid="boot.ant.15.classpath" />
+  </path>
+
+  <!-- ============================================================== -->
+  <!-- tools module: -->
+
+  <!-- compiled module classes + res + core.classpath: -->
+  <path id="tools.classpath" >
+    <pathelement location="${tools.classes.out.dir}" />
+    <pathelement location="${tools.res.dir}" />
+    <path refid="core.classpath" />
+  </path>
+
+  <!-- ========== END OF FILE ======================================= -->
diff --git a/lib/external/placeholder.exclude b/lib/external/placeholder.exclude
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/external/placeholder.exclude
diff --git a/lib/internal/placeholder.exclude b/lib/internal/placeholder.exclude
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/internal/placeholder.exclude
diff --git a/lib/internal/stamptool.jar b/lib/internal/stamptool.jar
new file mode 100644
index 0000000..1f8f089
--- /dev/null
+++ b/lib/internal/stamptool.jar
Binary files differ
diff --git a/module.dirs.properties b/module.dirs.properties
new file mode 100644
index 0000000..90fc1c2
--- /dev/null
+++ b/module.dirs.properties
@@ -0,0 +1,79 @@
+# -----------------------------------------------------------------------------
+# misc aliases:
+
+tools:                                          tools
+core:                                           core
+ant:                                            ant
+test:                                           test
+
+lib:                                            lib
+data:                                           data
+res:                                            res
+
+java:                                           java
+java12:                                         java12
+java13:                                         java13
+java14:                                         java14
+
+ant14:                                          ant14
+ant15:                                          ant15
+
+internal:                                       internal
+external:                                       external
+
+# -----------------------------------------------------------------------------
+# ---- module-specific static (checked-in) locations:
+
+# -----------------------------------------------------------------------------
+# lib module structure:
+
+lib.dir.root:                                   ${basedir}/${lib}
+
+internal.lib.dir:                               ${lib.dir.root}/${internal}
+external.lib.dir:                               ${lib.dir.root}/${external}
+
+# -----------------------------------------------------------------------------
+# core module structure:
+
+core.dir.root:                                  ${basedir}/${core}
+
+core.src.java12.dir:                            ${core.dir.root}/${java12}
+core.src.java13.dir:                            ${core.dir.root}/${java13}
+core.src.java14.dir:                            ${core.dir.root}/${java14}
+
+core.res.dir:                                   ${core.dir.root}/${res}
+core.data.dir:                                  ${core.dir.root}/${data}
+
+# -----------------------------------------------------------------------------
+# ant module structure:
+
+ant.dir.root:                                   ${basedir}/${ant}
+
+ant.src.ant14.dir:                              ${ant.dir.root}/${ant14}
+ant.src.ant15.dir:                              ${ant.dir.root}/${ant15}
+
+ant.res.dir:                                    ${ant.dir.root}/${res}
+ant.data.dir:                                   ${ant.dir.root}/${data}
+
+# -----------------------------------------------------------------------------
+# tools module structure:
+
+tools.dir.root:                                 ${basedir}/${tools}
+
+tools.src.dir:                                  ${tools.dir.root}/${java}
+
+tools.res.dir:                                  ${tools.dir.root}/${res}
+tools.data.dir:                                 ${tools.dir.root}/${data}
+
+# -----------------------------------------------------------------------------
+# test module structure:
+
+test.dir.root:                                   ${basedir}/${test}
+
+test.src.dir:                                    ${test.dir.root}/${java}
+
+test.res.dir:                                    ${test.dir.root}/${res}
+test.data.dir:                                   ${test.dir.root}/${data}
+
+# -----------------------------------------------------------------------------
+# end of file
diff --git a/release.properties b/release.properties
new file mode 100644
index 0000000..d27d234
--- /dev/null
+++ b/release.properties
@@ -0,0 +1,19 @@
+# -----------------------------------------------------------------------------
+
+# properties that control release major.minor labeling:
+
+app.major.version:	2
+app.minor.version:	0
+
+# properties affecting formatting of build id and date:
+
+app.build.id.format:	~Md~
+app.build.date.format:	~D T~
+
+# string used in the HTML content:
+
+app.bug.report.link:	http://sourceforge.net/projects/${app.short.name}
+app.home.site.link:		http://${app.short.name}.sourceforge.net/
+
+# -----------------------------------------------------------------------------
+# end of file
diff --git a/tools/java/com/vladium/tools/ClassDep.java b/tools/java/com/vladium/tools/ClassDep.java
new file mode 100644
index 0000000..05d06b9
--- /dev/null
+++ b/tools/java/com/vladium/tools/ClassDep.java
@@ -0,0 +1,220 @@
+/* Copyright (C) 2004 Vladimir Roubtsov. All rights reserved.
+ * 
+ * This program and the accompanying materials are made available under
+ * the terms of the Common Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * $Id: ClassDep.java,v 1.1.2.2 2004/07/09 01:42:04 vlad_r Exp $
+ */
+package com.vladium.tools;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import com.vladium.jcd.cls.ClassDef;
+import com.vladium.jcd.cls.IConstantCollection;
+import com.vladium.jcd.cls.constant.CONSTANT_Class_info;
+import com.vladium.jcd.cls.constant.CONSTANT_info;
+import com.vladium.jcd.parser.ClassDefParser;
+import com.vladium.util.ByteArrayOStream;
+import com.vladium.util.Descriptors;
+
+// ----------------------------------------------------------------------------
+/**
+ * TODO: doc
+ * 
+ * @author Vlad Roubtsov, (C) 2004
+ */
+public class ClassDep
+{
+    // public: ................................................................
+    
+    public static void main (final String [] args)
+        throws Exception
+    {
+        if (args.length < 2)
+            throw new IllegalArgumentException ("usage: classpath output_file rootset_classname_1 [rootset_classname_2 ...]");
+            
+        final String _classPath = args [0];
+        final URL [] classPath;
+        {
+            final StringTokenizer tokenizer = new StringTokenizer (_classPath, File.pathSeparator);
+            classPath = new URL [tokenizer.countTokens ()];
+            
+            for (int i = 0; tokenizer.hasMoreTokens (); ++ i)
+            {
+                classPath [i] = new File (tokenizer.nextToken ()).toURL ();
+            }
+        }
+        
+        final File outFile = new File (args [1]);
+        
+        final int offset = 2;
+        final String [] rootSet = args.length > offset
+            ? new String [args.length - offset]
+            : new String [0];
+        {
+            for (int a = 0; a < rootSet.length; ++ a)
+            {
+                rootSet [a] = args [a + offset];
+            }
+        }
+        
+        final ClassDep _this = new ClassDep (rootSet, classPath);
+        final String [] deps = _this.getDependencies (true);
+        
+        final StringBuffer s = new StringBuffer (); 
+        for (int d = deps.length - 1; d >= 0; -- d) // reverse topological order
+        {
+            s.append (deps [d]);
+            if (d > 0) s.append (',');
+        }
+        
+        final File parent = outFile.getParentFile ();
+        if (parent != null) parent.mkdirs ();
+        
+        final FileOutputStream out = new FileOutputStream (outFile);
+        
+        final Properties result = new Properties ();
+        result.setProperty ("closure", s.toString ());
+        
+        result.store (out, "this file is auto-generated, do not edit");
+        
+        out.close ();
+    }
+    
+    
+    public ClassDep (final String [] rootSet, final URL [] classPath)
+    {
+        if (rootSet == null)
+            throw new IllegalArgumentException ("null input: rootSet");
+        
+        if (classPath == null)
+            throw new IllegalArgumentException ("null input: classPath");
+        
+        m_rootSet = rootSet;
+        m_classPath = classPath;
+    }
+    
+    public String [] getDependencies (final boolean includeRootSet)
+        throws IOException
+    {
+        final Set /* class Java name:String */ _result = new HashSet ();
+        final ClassLoader loader = new URLClassLoader (m_classPath, null);
+        
+        if (includeRootSet)
+        {
+            for (int i = 0; i < m_rootSet.length; ++ i)
+            {
+                _result.add (m_rootSet [i]);
+            }
+        }
+        
+        final LinkedList /* class VM name:String */ queue = new LinkedList ();
+        for (int i = 0; i < m_rootSet.length; ++ i)
+        {
+            queue.add (Descriptors.javaNameToVMName (m_rootSet [i]));
+        }
+        
+        final ByteArrayOStream baos = new ByteArrayOStream (8 * 1024);
+        final byte [] readbuf = new byte [8 * 1024];
+        
+        while (! queue.isEmpty ())
+        {
+            final String classVMName = (String) queue.removeFirst ();
+            
+            // keep at most one file descriptor open:
+            
+            InputStream in = null;
+            try
+            {
+                // NOTE: getResources() not used
+                in = loader.getResourceAsStream (classVMName + ".class");
+                
+                if (in == null)
+                {
+                    throw new IllegalArgumentException ("class [" + Descriptors.vmNameToJavaName (classVMName) + "] not found in the input classpath");
+                }
+                else
+                {
+                    baos.reset ();
+                    for (int read; (read = in.read (readbuf)) >= 0; baos.write (readbuf, 0, read));
+                }
+            }
+            finally
+            {
+                if (in != null) try { in.close (); } catch (IOException ignore) { ignore.printStackTrace (); }
+            }
+            in = null;
+
+            final ClassDef cls = ClassDefParser.parseClass (baos.getByteArray (), baos.size ());
+            final List /* class VM name:String */ clsDeps  = getCONSTANT_Class_info (cls);
+            
+            for (Iterator i = clsDeps.iterator (); i.hasNext (); )
+            {
+                final String classDepVMName = (String) i.next ();
+                
+                if (classDepVMName.startsWith ("com/vladium/")) // TODO: generic filtering
+                {
+                    if (_result.add (Descriptors.vmNameToJavaName (classDepVMName)))
+                    {
+                        queue.addLast (classDepVMName);
+                    }
+                }
+            }
+        }
+        
+        final String [] result = new String [_result.size ()];
+        _result.toArray (result);
+        
+        return result;
+    }
+    
+    /**
+     * @return array of class VM names [may contain duplicates]
+     */
+    public static List getCONSTANT_Class_info (final ClassDef cls)
+    {
+        if (cls == null)
+            throw new IllegalArgumentException ("null input: cls");
+        
+        final IConstantCollection constants = cls.getConstants ();
+        final IConstantCollection.IConstantIterator i = constants.iterator ();
+        
+        final List result = new ArrayList ();
+
+        for (CONSTANT_info entry; (entry = i.nextConstant ()) != null; )
+        {
+            if (entry instanceof CONSTANT_Class_info)
+            {
+                result.add (((CONSTANT_Class_info) entry).getName (cls));
+            }
+        }
+        
+        return result;
+    }
+    
+    // protected: .............................................................
+
+    // package: ...............................................................
+    
+    // private: ...............................................................
+
+
+    private final String [] m_rootSet;
+    private final URL [] m_classPath;
+    
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/work.dirs.properties b/work.dirs.properties
new file mode 100644
index 0000000..e215fa1
--- /dev/null
+++ b/work.dirs.properties
@@ -0,0 +1,52 @@
+# -----------------------------------------------------------------------------
+# misc aliases:
+
+classes:                                        classes
+files:                                          files
+srcgen:                                         srcgen
+
+# -----------------------------------------------------------------------------
+# ---- root locations created by the build:
+
+# root dir of all build output:
+out.dir:                                        ${basedir}/out
+
+# distribution dir:
+dist.dir:                                       ${basedir}/dist
+
+# distribution dir:
+release.dir:                                    ${basedir}/release
+
+# directory for scratch files:
+temp.dir:                                       ${out.dir}/temp
+
+# -----------------------------------------------------------------------------
+# ---- module-specific locations created by the build:
+
+# -----------------------------------------------------------------------------
+# core module:
+
+core.classes.out.dir:                           ${out.dir}/${core}/${classes}
+core.srcgen.out.dir:                            ${out.dir}/${core}/${srcgen}
+core.res.out.dir:                               ${out.dir}/${core}/${res}
+
+# -----------------------------------------------------------------------------
+# ant module:
+
+ant.classes.out.dir:                            ${out.dir}/${ant}/${classes}
+ant.srcgen.out.dir:                             ${out.dir}/${ant}/${srcgen}
+
+# -----------------------------------------------------------------------------
+# tools module:
+
+tools.classes.out.dir:                          ${out.dir}/${tools}/${classes}
+tools.srcgen.out.dir:                           ${out.dir}/${tools}/${srcgen}
+
+# -----------------------------------------------------------------------------
+# test module:
+
+test.classes.out.dir:                           ${out.dir}/${test}/${classes}
+test.srcgen.out.dir:                            ${out.dir}/${test}/${srcgen}
+
+# -----------------------------------------------------------------------------
+# end of file