Author: nextgens
Date: 2006-11-10 20:10:52 +0000 (Fri, 10 Nov 2006)
New Revision: 10867

Added:
   trunk/contrib/fec/common/
   trunk/contrib/fec/common/ChangeLog
   trunk/contrib/fec/common/LICENSE
   trunk/contrib/fec/common/build.properties
   trunk/contrib/fec/common/build.xml
   trunk/contrib/fec/common/lib/
   trunk/contrib/fec/common/src/
   trunk/contrib/fec/common/src/com/
   trunk/contrib/fec/common/src/com/onionnetworks/
   trunk/contrib/fec/common/src/com/onionnetworks/io/
   trunk/contrib/fec/common/src/com/onionnetworks/io/AuditableRaf.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/BlockDigestInputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/BlockingRAF.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/CommitRaf.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/ExceptionRAF.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrity.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrityImpl.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/FilterRAF.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/FiniteInputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/JoiningInputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/Journal.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/JournalingRAF.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/LazyRenameRAF.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/RAF.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/RAFInputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/RAFOutputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/TempRaf.java
   
trunk/contrib/fec/common/src/com/onionnetworks/io/UnpredictableInputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/WriteCommitRaf.java
   trunk/contrib/fec/common/src/com/onionnetworks/io/WriteOnceRaf.java
   trunk/contrib/fec/common/src/com/onionnetworks/net/
   trunk/contrib/fec/common/src/com/onionnetworks/net/DatagramSocketFactory.java
   
trunk/contrib/fec/common/src/com/onionnetworks/net/PlainDatagramSocketFactory.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/
   trunk/contrib/fec/common/src/com/onionnetworks/util/AsyncPersistentProps.java
   
trunk/contrib/fec/common/src/com/onionnetworks/util/BlockDigestInputStream.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/Buffer.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionEvent.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionHandler.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrity.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrityImpl.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/FileUtil.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/FilteringIterator.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/IntIterator.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/InvokeEvent.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/InvokingDispatch.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/JoiningIterator.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/NativeDeployer.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/NetUtil.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/Range.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/RangeSet.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/RateCalculator.java
   
trunk/contrib/fec/common/src/com/onionnetworks/util/ReflectiveEventDispatch.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/SimUtil.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/TimedSoftHashMap.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/Tuple.java
   trunk/contrib/fec/common/src/com/onionnetworks/util/Util.java
   trunk/contrib/fec/common/test/
   trunk/contrib/fec/common/test/build.xml
   trunk/contrib/fec/common/test/src/
   trunk/contrib/fec/common/test/src/com/
   trunk/contrib/fec/common/test/src/com/onionnetworks/
   trunk/contrib/fec/common/test/src/com/onionnetworks/io/
   trunk/contrib/fec/common/test/src/com/onionnetworks/io/BlockingRAFTest.java
   
trunk/contrib/fec/common/test/src/com/onionnetworks/io/FiniteInputStreamTest.java
   
trunk/contrib/fec/common/test/src/com/onionnetworks/io/WriteCommitRafTest.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/
   
trunk/contrib/fec/common/test/src/com/onionnetworks/util/AsyncPersistentPropsTest.java
   
trunk/contrib/fec/common/test/src/com/onionnetworks/util/BlockDigestInputStreamTest.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/BzeroTest.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/Char2Bytes.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/Hex2Bytes.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/Log2Test.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeSetTest.java
   trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeTest.java
   trunk/contrib/fec/common/tools/
   trunk/contrib/fec/common/tools/build.xml
   trunk/contrib/fec/common/tools/src/
   trunk/contrib/fec/common/tools/src/com/
   trunk/contrib/fec/common/tools/src/com/onionnetworks/
   trunk/contrib/fec/common/tools/src/com/onionnetworks/util/
   
trunk/contrib/fec/common/tools/src/com/onionnetworks/util/RateCalculatorTest.java
Log:
contrib: import the official onion-common into our tree

Added: trunk/contrib/fec/common/ChangeLog
===================================================================
--- trunk/contrib/fec/common/ChangeLog  2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/ChangeLog  2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,58 @@
+9/26/2002:
+
+o AsyncPersistentProps properly throws Exceptions.
+o safeOnionFile now ensures file existance.
+
+5/29/2002:
+
+o JoiningInputStream added
+
+5/08/2002:
+
+o LazyRenameRAF refactored out of ScratchRAF
+o BlockingRAF is now a FilterRAF
+
+5/07/2002:
+
+o FilterRAF created
+o RAF.getFile() added
+o Journal created
+o JournalingRAF created
+o getUserTempDir and createTempFile moved to FileUtil from ScratchRAF
+
+5/03/2002:
+
+o contains(Range) added to RangeSet
+
+4/28/2002:
+
+o BlockingRAF now supports look-aside buffers.  Thus, increasing the size
+of the buffers passed into a WebRAID InputStream can significantly decrease
+the amount of IO that takes place while streaming.
+
+o ReflectiveEventDispatch CPU utilization has been improved significantly.
+
+3/12/2002: 
+
+Added RAF.length()
+Optimized RAFInputStream.skip()
+ 
+bourbon-14:
+
+o clear() added to AsyncPersistentProps
+o Fixed RangeSet AIOOB when RangeSet is empty.
+
+bourbon-12:
+
+o Check for min > max in RangeSet.add added.
+o ReflectiveEventDispatcher now uses ExceptionHandler.
+
+bourbon-11:
+
+o ExceptionHandler interface added.
+
+bourbon-6:
+
+o NativeDeployer now threadsafe
+o Revamped NativeDeployer properties format.
+

Added: trunk/contrib/fec/common/LICENSE
===================================================================
--- trunk/contrib/fec/common/LICENSE    2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/LICENSE    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,32 @@
+/*
+ * Common Java Utilities
+ *
+ * Copyright (C) 2000-2001 Justin Chapweske (justin at chapweske.com)
+ * Copyright (C) 2000-2001 Ry4an Brase (ry4an at ry4an.org) 
+ * Copyright (C) 2001 Onion Networks
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+

Added: trunk/contrib/fec/common/build.properties
===================================================================
--- trunk/contrib/fec/common/build.properties   2006-11-10 20:02:12 UTC (rev 
10866)
+++ trunk/contrib/fec/common/build.properties   2006-11-10 20:10:52 UTC (rev 
10867)
@@ -0,0 +1,40 @@
+# global properties for this build
+
+appname=common
+app.jar=${lib}/onion-${appname}.jar
+tools.jar=${lib}/onion-${appname}-tools.jar
+https.jar=${lib}/onion-${appname}-https.jar
+
+classpath=${app.jar}
+tools.classpath=${classpath};${tools.jar}
+test.classpath=${tools.classpath}
+
+package=com.onionnetworks
+packagepath=com/onionnetworks/
+
+src=${basedir}/src
+src1.4=${basedir}/src1.4
+lib=${basedir}/lib
+classes=${basedir}/classes
+javadoc=${basedir}/javadoc
+
+test=${basedir}/test
+test.src=${test}/src
+test.classes=${test}/classes
+test.results=${test}/results
+
+tools=${basedir}/tools
+tools.src=${tools}/src
+tools.classes=${tools}/classes
+tools.javadoc=${tools}/javadoc
+tools.package=${package}.util
+
+build.compiler=jikes
+javac.debug=on
+javac.optimize=off
+javac.deprecation=off
+
+junit.jar=${test.lib}/junit-3.7.jar
+
+
+

Added: trunk/contrib/fec/common/build.xml
===================================================================
--- trunk/contrib/fec/common/build.xml  2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/build.xml  2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,143 @@
+<!--
+The default target is "all".
+
+Other targets:
+
+  clean         - Remove all generated files.
+  classes       - Builds the classes.
+  jars          - Creates the jars.
+  all           - builds everything
+  prepare       - Set up build directory structure.
+  dist          - Constructs a distribution file.
+  javadoc       - Builds the API documentation.
+  demo          - Runs the demo application.
+  test          - Runs the junit test harnesses.
+
+-->
+<project name="Common" default="all" basedir=".">
+  <property environment="env"/>
+  <property file="build.properties"/>
+
+  <!-- ==================================================================== -->
+  <target name="prepare">
+    <mkdir dir="${javadoc}" />
+    <mkdir dir="${classes}" />
+    <mkdir dir="${lib}" />
+
+    <available property="jdk1.4.available" 
+              classname="java.util.logging.Handler" />
+
+    <available property="jsse.available" 
+              classname="com.sun.net.ssl.internal.ssl.Provider" />
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="all" depends="jars,tools"/>
+
+  <!-- ==================================================================== -->
+  <target name="tidy"
+         description="Remove generated files not needed for running">
+
+    <delete dir="${classes}" quiet="true"/>
+    <ant dir="${test}" inheritAll="true" target="tidy"/>
+    <ant dir="${tools}" inheritAll="true" target="tidy"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="clean" depends="tidy"
+         description="Remove generated files">
+        
+    <delete dir="${javadoc}" quiet="true"/>
+    <delete file="${app.jar}" quiet="true"/>
+
+    <ant dir="${test}" inheritAll="true" target="clean"/>
+    <ant dir="${tools}" inheritAll="true" target="clean"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="classes" depends="prepare"
+   description="Compile the java classes" >
+    <copy todir="${classes}">
+      <fileset dir="${src}">
+        <include name="**/*.properties" />
+      </fileset>
+    </copy>
+    <javac srcdir="${src}"
+           destdir="${classes}"
+          classpath="${classpath}"
+          debug="${javac.debug}"
+           optimize="${javac.optimize}"
+           deprecation="${javac.deprecation}"
+           >
+       <include name="**/*.java"/>
+    </javac>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="classes1.4" depends="prepare" if="jdk1.4.available"
+   description="Compile the java 1.4 classes" >
+
+    <copy todir="${classes}">
+      <fileset dir="${src1.4}">
+        <include name="**/*.properties" />
+      </fileset>
+    </copy>
+    <javac srcdir="${src1.4}"
+           destdir="${classes}"
+          classpath="${classpath}"
+           debug="${javac.debug}"
+           optimize="${javac.optimize}"
+           deprecation="${javac.deprecation}"
+           >
+       <include name="**/*.java"/>
+    </javac>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="jars" depends="classes"
+         description="Build the jar files">
+    <jar jarfile="${app.jar}" basedir="${classes}"> 
+<!--    manifest="${src}/MANIFEST.MF" -->
+
+       <include name="${packagepath}/**"/>
+    </jar>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="javadoc" depends="jars"
+   description="Build the javadoc">
+    <mkdir dir="${javadoc}"/>
+    <javadoc packagenames="${package}.*"
+             sourcepath="${src}"
+            classpath="${classpath}"
+             destdir="${javadoc}"
+             author="true"
+             version="true"
+             public="true"
+             windowtitle="${ant.project.name} API"
+             doctitle="${ant.project.name}"
+             bottom="Copyright &#169; 2002 Onion Networks. All Rights 
Reserved.">
+      <link href="http://onionnetworks.com/fec/javadoc/"/>
+      <link href="http://java.sun.com/products/jdk/1.3/docs/api/"/>
+    </javadoc>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="test" depends="jars"
+   description="Build and run the test harnesses">
+    <ant dir="${test}" inheritAll="true" target="test"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="tools" depends="jars"
+         description="builds the tools">
+    <ant dir="${tools}" inheritAll="true" target="jars"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="demo" depends="tools" 
+         description="Build and run the demo">
+    
+    <ant dir="${tools}" inheritAll="true" target="demo"/>
+  </target>
+</project>

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/AuditableRaf.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/AuditableRaf.java 
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/AuditableRaf.java 
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,36 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+
+public abstract class AuditableRaf extends FilterRAF {
+
+    protected String defaultUri;
+    
+    public AuditableRaf(RAF raf) {
+       this(raf,null);
+    }
+
+    public AuditableRaf(RAF raf, String defaultUri) {
+       super(raf);
+       this.defaultUri = defaultUri;
+    }
+
+    public synchronized String getDefaultUri() {
+       return defaultUri;
+    }
+
+    public synchronized void setDefaultUri(String uri) {
+       this.defaultUri = uri;
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       if (defaultUri == null) {
+           throw new IllegalStateException("defaultUri is null");
+       }
+       seekAndWrite(defaultUri,pos,b,off,len);
+    }
+
+    public abstract void seekAndWrite(String uri, long pos, byte[] b, int off,
+                                     int len) throws IOException;
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/io/BlockDigestInputStream.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/io/BlockDigestInputStream.java   
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/io/BlockDigestInputStream.java   
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,89 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.Buffer;
+import java.io.*;
+import java.util.ArrayList;
+import java.security.*;
+
+/**
+ * @author Justin F. Chapweske
+ */
+public class BlockDigestInputStream extends FilterInputStream {
+
+    protected MessageDigest md;
+    protected int blockSize, byteCount;
+    ArrayList digestList = new ArrayList();
+    Buffer[] digests = null;
+
+    public BlockDigestInputStream(InputStream is, String algorithm, 
+                                  int blockSize) 
+        throws NoSuchAlgorithmException {
+        
+        super(is);
+        if (blockSize <= 0) {
+            throw new IllegalArgumentException("blockSize must be > 0");
+        }
+        this.md = MessageDigest.getInstance(algorithm);
+        this.blockSize = blockSize;
+    }
+
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        if (read(b,0,1) == -1) {
+            return -1;
+        }
+        return b[0] & 0xFF;
+    }
+
+    public long skip(long n) throws IOException {
+        byte[] b = new byte[n < 1024 ? (int)n : 1024];
+        long l = n;
+        int c;
+        while (l > 0) {
+            if ((c = read(b, 0, l < 1024 ? (int)l : 1024)) == -1) {
+                break;
+            }
+            l -= c;
+        }
+        return n - l;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        int left = blockSize-byteCount;
+        int c;
+        // truc the read if they want more than is left for this block.
+        if ((c = in.read(b,off,len < left ? len : left)) == -1) {
+            return -1;
+        }
+        md.update(b,off,c);        
+        byteCount += c;
+        // this block is full
+        if(byteCount == blockSize) {
+            digestList.add(new Buffer(md.digest()));        
+            byteCount = 0;
+        }
+        return c;
+    }
+
+    public void finish() {
+        if (byteCount != 0) {
+            digestList.add(new Buffer(md.digest()));
+        }
+        digests = (Buffer[]) digestList.toArray(new Buffer[0]);
+        digestList = null;
+    }
+
+    public void close() throws IOException {
+        if (digestList != null) {
+            finish();
+        }
+        in.close();
+    }
+
+    public Buffer[] getBlockDigests() {
+        if (digests == null) {
+            throw new IllegalStateException("Must call finish or close first");
+        }
+        return digests;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/BlockingRAF.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/BlockingRAF.java  
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/BlockingRAF.java  
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,183 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+
+public class BlockingRAF extends FilterRAF {
+
+    RangeSet written = new RangeSet();
+    IOException e;
+
+    // Make sure not to key buffers off of a Range, or any other non-unique
+    // object, as multiple readers may be using the same key and will trash
+    // each other.
+    HashMap buffers = new HashMap();
+
+    public BlockingRAF(RAF raf) {
+       super(raf);
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       // exception
+       if (e != null) {
+           throw e;
+       }       
+
+       raf.seekAndWrite(pos,b,off,len);
+
+       // call this after seekAndWrite() to allow exceptions to be thrown, if
+       // there are any.
+       if (len == 0) {
+           return;
+       }
+
+       fillBlockedBuffers(pos,b,off,len);
+
+       written.add(pos,pos+len-1);
+       this.notifyAll();
+    }
+
+    private synchronized void fillBlockedBuffers(long pos, byte[] b, int off, 
+                                                int len) {
+       if (buffers.isEmpty()) {
+           return;
+       }
+
+       Range r = new Range(pos,pos+len-1);
+       
+       for (Iterator it = buffers.keySet().iterator();it.hasNext();) {
+           Object key = (Object) it.next();
+           Tuple t = (Tuple) buffers.get(key);
+           Range r2 = (Range) t.getLeft();
+           Buffer buf = (Buffer) t.getRight();
+           
+           // Get the range in common.
+           long min = Math.max(r.getMin(),r2.getMin());
+           long max = Math.min(r.getMax(),r2.getMax());
+           
+           if (min <= max) {  
+               // there is something in common
+
+               // copy the data to the proper place in the buffer
+               //
+               // (int) casts are safe because they can't be larger than len
+               System.arraycopy(b,(int) (off+(min-r.getMin())),
+                                buf.b,(int) (buf.off+(min-r2.getMin())),
+                                (int) (max-min+1));
+           }
+       }
+    }
+
+    public synchronized void seekAndReadFully(long pos, byte[] b, int off,
+                                             int len) throws IOException {
+       throw new IOException("unsupported operation");
+    }
+
+    public synchronized int seekAndRead(long pos, byte[] b, int off,
+                                       int len) throws IOException {
+
+       // Will the bytes be written directly to the buffer?
+       boolean directWrite = false;
+
+       // This is the range we are currently interested in.
+       Range r = null;
+       // This is the key we use to access the buffers when stored
+       // for direct write.
+       Object key = new Object();
+
+       while (!isClosed() && e == null && !getMode().equals("r") && len != 0){
+
+           if (r == null) {
+               // This is the range we are interested in.
+               r = new Range(pos,pos+len-1);
+           }
+
+           // Get the ranges in common.
+           RangeSet rs = new RangeSet();
+           rs.add(r);
+           RangeSet avail = written.intersect(rs);
+           Range first = null;
+           if (!avail.isEmpty()) {
+               first = (Range) avail.iterator().next();
+           }
+
+           if (written.contains(pos)) {
+               
+               if (directWrite) {
+                   // The data was written directly to the buffer.
+                   return (int) first.size();
+               } else {
+                   // (int) cast is safe because size() can't be larger than 
+                   // len
+                   return raf.seekAndRead(pos,b,off,(int) first.size());
+               }
+           } else {
+
+               // The data will be written directly to the buffer.
+               directWrite = true;
+
+               if (first != null) {
+                   // Change the range of interest to only include bytes which
+                   // have yet to be written.
+                   r = new Range(pos,first.getMin()-1);
+               }
+
+               // Make the buffer available to be written to.
+               buffers.put(key, new Tuple(r,new Buffer(b,off,len)));
+
+               try {
+                   this.wait();
+               } catch (InterruptedException e) {
+                   throw new InterruptedIOException(e.getMessage());
+               } finally {
+                   buffers.remove(key);
+               }
+           }
+       }
+
+       // exception
+       if (e != null) {
+           throw e;
+       }
+
+       // We only block during r/w mode.  For read-only we use the
+       // normal behavior.
+       if (getMode().equals("r")) {
+           return raf.seekAndRead(pos,b,off,len);
+       }
+
+       // RAF closed
+       if (isClosed()) {
+           throw new IOException("RAF closed");
+       }
+
+       // zero len read.  exceptions take priority.
+       if (len == 0) {
+           return 0;
+       }
+
+       // This should never happen.
+       throw new IllegalStateException("Method should have already "+
+                                       "returned.");
+    }
+    
+    public synchronized void setReadOnly() throws IOException {
+       raf.setReadOnly();
+       this.notifyAll();
+    }
+    
+    public synchronized void setException(IOException e) {
+       this.e = e;
+       this.notifyAll();
+    }
+
+    public synchronized void close() throws IOException {
+       raf.close();
+       this.notifyAll();
+    }
+}
+
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/CommitRaf.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/CommitRaf.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/CommitRaf.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,199 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+
+public class CommitRaf extends FilterRAF {
+
+    RangeSet committed = new RangeSet();
+    IOException e;
+    
+    // Make sure not to key buffers off of a Range, or any other non-unique
+    // object, as multiple readers may be using the same key and will trash
+    // each other.
+    HashMap buffers = new HashMap();
+    
+    public CommitRaf(RAF raf) {
+       super(raf);
+    }
+    
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       // exception
+       if (e != null) {
+           throw e;
+       }       
+
+       // wait on len == 0 action to allow exceptions to be thrown.
+       if (len != 0) {
+           // check if any of the bytes have already been committed
+           Range r = new Range(pos,pos+len-1);
+           RangeSet rs = new RangeSet();
+           rs.add(r);
+           if (!committed.intersect(rs).isEmpty()) {
+               throw new IOException("Illegal write attempt.  Parts of range "+
+                                     "already committed. :"+r);
+           }
+       }
+       
+       raf.seekAndWrite(pos,b,off,len);
+       
+       // call this after seekAndWrite() to allow exceptions to be thrown, if
+       // there are any.
+       if (len == 0) {
+           return;
+       }
+       
+       fillBlockedBuffers(pos,b,off,len);
+    }
+
+    public synchronized void commit(Range r) {
+       committed.add(r);
+       this.notifyAll();
+    }
+
+    public synchronized void commit(RangeSet rs) {
+       committed.add(rs);
+       this.notifyAll();
+    }
+
+    private synchronized void fillBlockedBuffers(long pos, byte[] b, int off, 
+                                                int len) {
+       if (buffers.isEmpty()) {
+           return;
+       }
+
+       Range r = new Range(pos,pos+len-1);
+       
+       // Iterate through the blocked readers and fill their buffers.
+       for (Iterator it = buffers.keySet().iterator();it.hasNext();) {
+           Object key = (Object) it.next();
+           Tuple t = (Tuple) buffers.get(key);
+           Range r2 = (Range) t.getLeft();
+           Buffer buf = (Buffer) t.getRight();
+           
+           // Get the range in common.
+           long min = Math.max(r.getMin(),r2.getMin());
+           long max = Math.min(r.getMax(),r2.getMax());
+           
+           if (min <= max) {  
+               // there is something in common
+
+               // copy the data to the proper place in the buffer
+               //
+               // (int) casts are safe because they can't be larger than len
+               System.arraycopy(b,(int) (off+(min-r.getMin())),
+                                buf.b,(int) (buf.off+(min-r2.getMin())),
+                                (int) (max-min+1));
+           }
+       }
+    }
+
+    public synchronized void seekAndReadFully(long pos, byte[] b, int off,
+                                             int len) throws IOException {
+       throw new IOException("unsupported operation");
+    }
+
+    public synchronized int seekAndRead(long pos, byte[] b, int off,
+                                       int len) throws IOException {
+
+       // Will the bytes be written directly to the buffer?
+       boolean directWrite = false;
+
+       // This is the range we are currently interested in.
+       Range r = null;
+       // This is the key we use to access the buffers when stored
+       // for direct write.
+       Object key = new Object();
+
+       while (!isClosed() && e == null && len != 0){
+
+           // If the file is read-only and the whole thing is commited, then
+           // we read directly from the underlying Raf.  This is so that -1's
+           // get returned at EOF when the file is completedly committed.
+           if (getMode().equals("r") &&
+               (length() == 0 || 
+                committed.equals(new RangeSet(new Range(0,length()-1))))) {
+               
+               return raf.seekAndRead(pos,b,off,len);
+           }
+
+           if (r == null) {
+               // This is the range we are interested in.
+               r = new Range(pos,pos+len-1);
+           }
+
+           // Get the ranges in common.
+           RangeSet rs = new RangeSet();
+           rs.add(r);
+           RangeSet avail = committed.intersect(rs);
+           Range first = null;
+           if (!avail.isEmpty()) {
+               first = (Range) avail.iterator().next();
+           }
+
+           if (committed.contains(pos)) {
+               
+               if (directWrite) {
+                   // The data was written directly to the buffer.
+                   return (int) first.size();
+               } else {
+                   // (int) cast is safe because size() can't be larger than 
+                   // len
+                   return raf.seekAndRead(pos,b,off,(int) first.size());
+               }
+           } else {
+
+               // The data will be written directly to the buffer.
+               directWrite = true;
+
+               if (first != null) {
+                   // Change the range of interest to only include bytes which
+                   // have yet to be committed.
+                   r = new Range(pos,first.getMin()-1);
+               }
+
+               // Make the buffer available to be written to.
+               buffers.put(key, new Tuple(r,new Buffer(b,off,len)));
+
+               try {
+                   this.wait();
+               } catch (InterruptedException e) {
+                   throw new InterruptedIOException(e.getMessage());
+               } finally {
+                   buffers.remove(key);
+               }
+           }
+       }
+
+       // exception
+       if (e != null) {
+           throw e;
+       }
+
+       // RAF closed
+       if (isClosed()) {
+           throw new IOException("RAF closed");
+       }
+
+       // zero len read.  exceptions take priority.
+       if (len == 0) {
+           return 0;
+       }
+
+       // This should never happen.
+       throw new IllegalStateException("Method should have already "+
+                                       "returned.");
+    }
+    
+    public synchronized void setException(IOException e) {
+       this.e = e;
+       this.notifyAll();
+    }
+
+    public synchronized void close() throws IOException {
+       raf.close();
+       this.notifyAll();
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/ExceptionRAF.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/ExceptionRAF.java 
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/ExceptionRAF.java 
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,39 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+
+public class ExceptionRAF extends RAF {
+
+    IOException e;
+
+    public ExceptionRAF(IOException e, String mode) {
+       this.e = e;
+       // FIX, this is rediculous, but check the mode.
+       this.mode = mode;
+       // This is so getMode() calls will succeed.
+    }
+
+    public void seekAndWrite(long pos, byte[] b, int off, 
+                            int len) throws IOException {
+       throw e;
+    }
+
+    public void seekAndReadFully(long pos, byte[] b, int off,
+                                int len) throws IOException {
+       throw e;
+    }
+
+    public void renameTo(File destFile) throws IOException {
+       throw e;
+    }
+
+    public synchronized void setReadOnly() throws IOException {
+       throw e;
+    }
+
+    public synchronized void setLength(long len) throws IOException {
+       throw e;
+    }
+
+    public synchronized void close() throws IOException {}
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrity.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrity.java        
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrity.java        
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,49 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.Buffer;
+
+/**
+ * This interface provides access to a number of block hashes for a file.  
+ * These hashes can be used to check the integrity of a single block and the
+ * overall file.
+ */
+public interface FileIntegrity {
+
+    /**
+     * @return the message digest algorithm used to create the the hashes.
+     */
+    public String getAlgorithm();
+
+    /**
+     * Specifies the size of each block.  This size should be a power of 2
+     * and all blocks will be this size, expect for the last block which may
+     * be equal to or less than the block size (but not 0).
+     *
+     * @return the block size.
+     */
+    public int getBlockSize();
+
+    /**
+     * @return the size of the file.
+     */
+    public long getFileSize();
+
+    /**
+     * Returns the number of blocks that make up the file.  This value will
+     * be equal to ceil(fileSize/blockSize).
+     *
+     * @return the block count.
+     */
+    public int getBlockCount();
+
+    /**
+     * @return the hash of the specified block.
+     */
+    public Buffer getBlockHash(int blockNum);
+
+    /**
+     * @return the hash of the entire file.
+     */
+    public Buffer getFileHash();
+
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrityImpl.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrityImpl.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/FileIntegrityImpl.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,98 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.Util;
+import com.onionnetworks.util.Buffer;
+
+/**
+ * This class provides a way to access various structures needed to check
+ * the integrity of a file.
+ *
+ * @author Justin F. Chapweske
+ */
+public class FileIntegrityImpl implements FileIntegrity {
+
+    private String algo;
+    private Buffer fileHash;
+    private Buffer[] blockHashes;
+    private int blockSize, blockCount;
+    private long fileSize;
+    
+
+    public FileIntegrityImpl(String algorithm, Buffer fileHash, 
+                             Buffer[] blockHashes, long fileSize, 
+                             int blockSize) {
+        if (algorithm == null) {
+            throw new NullPointerException("algorithm is null");
+        } else if (fileHash == null) {
+            throw new NullPointerException("fileHash is null");
+        } else if (blockHashes == null) {
+            throw new NullPointerException("blockHashes are null");
+        } else if (fileSize < 0) {
+            throw new IllegalArgumentException("fileSize < 0");
+        } else if (blockSize < 0) {
+            throw new IllegalArgumentException("blockSize < 0");
+        }
+        this.algo = algorithm;
+        this.fileHash = fileHash;
+        this.blockHashes = blockHashes;
+        this.fileSize = fileSize;
+        this.blockSize = blockSize;
+        this.blockCount = Util.divideCeil(fileSize,blockSize);
+        if (blockHashes.length != blockCount) {
+            throw new IllegalArgumentException("Incorrect block hash count");
+        }
+    }
+
+    /**
+     * @return the message digest algorithm used to create the the hashes.
+     */
+    public String getAlgorithm() {
+        return algo;
+    }
+
+    /**
+     * Specifies the size of each block.  This size should be a power of 2
+     * and all blocks will be this size, expect for the last block which may
+     * be equal to or less than the block size (but not 0).
+     *
+     * @return the block size.
+     */
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * @return the size of the file.
+     */
+    public long getFileSize() {
+        return fileSize;
+    }
+        
+    /**
+     * Returns the number of blocks that make up the file.  This value will
+     * be equal to ceil(fileSize/blockSize).
+     *
+     * @return the block count.
+     */
+    public int getBlockCount() {
+        return blockCount;
+    }
+ 
+
+    /**
+     * @return the hash of the specified block.
+     */
+    public Buffer getBlockHash(int blockNum) {
+        if (blockNum < 0 || blockNum >= blockCount) {
+            throw new IllegalArgumentException("Invalide block #"+blockNum);
+        }
+        return blockHashes[blockNum];
+    }
+
+    /**
+     * @return the hash of the entire file.
+     */
+    public Buffer getFileHash() {
+        return fileHash;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/FilterRAF.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/FilterRAF.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/FilterRAF.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,69 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+
+public abstract class FilterRAF extends RAF {
+
+    protected RAF raf;
+
+    public FilterRAF(RAF raf) {
+        this.raf = raf;
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       raf.seekAndWrite(pos,b,off,len);
+    }
+
+    public synchronized int seekAndRead(long pos, byte[] b, int off, int len) 
+       throws IOException {
+       
+       return raf.seekAndRead(pos,b,off,len);
+    }
+
+    public synchronized void seekAndReadFully(long pos, byte[] b, int off,
+                                              int len) throws IOException {
+       raf.seekAndReadFully(pos,b,off,len);
+    }
+
+    public synchronized void renameTo(File destFile) throws IOException {
+       raf.renameTo(destFile);
+    }
+
+    public synchronized String getMode() {
+       return raf.getMode();
+    }
+
+    public synchronized boolean isClosed() {
+       return raf.isClosed();
+    }
+
+    public synchronized File getFile() {
+       return raf.getFile();
+    }
+
+    public synchronized void setReadOnly() throws IOException {
+       raf.setReadOnly();
+    }
+
+    public synchronized void deleteOnClose() {
+       raf.deleteOnClose();
+    }
+
+
+    public synchronized void setLength(long len) throws IOException {
+        raf.setLength(len);
+    }
+
+    public synchronized long length() throws IOException {
+       return raf.length();
+    }
+
+    public synchronized void close() throws IOException {
+        raf.close();
+    }
+
+    public String toString() {
+       return raf.toString();
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/FiniteInputStream.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/FiniteInputStream.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/FiniteInputStream.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,92 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ *
+ * This class provides a FilterInputStream that only allows a finite number
+ * of bytes to be read, even if the actual InputStream is much longer.  This
+ * is useful for demultiplexing operations where there may be a number of
+ * sub-InputStreams available in a parent InputStream.
+ *
+ * Once the specified number of bytes have been read, a -1 will be returned.
+ * If the underlying inputstream returns a -1 before the specified number
+ * of bytes have been read, an EOFException will be thrown.  If one does
+ * NOT close() the FiniteInputStream, the parent InputStream can still be 
+ * used to read additional data.  Calling close() on the FiniteInputStream
+ * will close the parent InputStream. 
+ *
+ */
+public class FiniteInputStream extends FilterInputStream {
+
+    protected long left;
+
+    /**
+     * @param is The parent InputStream to read from.
+     * @param count the total number of bytes to allow to be read from
+     * the parent.
+     */
+    public FiniteInputStream(InputStream is, long count) { 
+        super(is);
+       if (is == null) {
+           throw new NullPointerException();
+       }
+       if (count < 0) {
+           throw new IllegalArgumentException("count must be > 0");
+       }
+       left = count;
+    }
+
+    /**
+     * wraps read(byte[],int,int) to read a single byte.
+     */
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        if (read(b,0,1) == -1) {
+            return -1;
+        }
+        return b[0] & 0xFF;
+    }
+
+    /**
+     * Read some bytes.  This will read no more total bytes from the parent
+     * than the count specified in the constructor.
+     *
+     * @return The number of bytes read, or -1 if the specified number
+     * of bytes for the FiniteInputStream have been read.
+     * @throws EOFException If the parent stream unexpectantly ends before the
+     * <code>count</code> bytes have been read.
+     */
+    public int read(byte[] b, int off, int len) throws IOException {
+       // check the len so that a 0 len returns a 0 result
+       if (left == 0 && len > 0) {
+           return -1;
+       }
+
+        // trunc the read if they want more than is left.
+       //FIX unit test the LONG
+       // The (int) cast is safe because len is an int and thus left will not
+       // return if it would overflow an int.
+        int c = in.read(b,off,(int) Math.min(len,left));
+       if (c < 0) {
+           throw new EOFException();
+        }
+       left -= c;
+        return c;
+    }
+
+    public long skip(long n) throws IOException {
+       long result = in.skip(Math.min(n,left));
+       left -= result;
+       return result;
+    }
+
+    public int available() throws IOException {
+       // (int) cast is safe because in.available must be an int and thus
+       // smaller than overflow.
+       return (int) Math.min(in.available(),left);
+    } 
+}
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/JoiningInputStream.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/JoiningInputStream.java   
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/JoiningInputStream.java   
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,48 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import java.net.*;
+
+public class JoiningInputStream extends FilterInputStream {
+
+    InputStream first, second;
+
+    /**
+     * @param first The first InputStream to read from
+     * @param second The second InputStream to read from 
+     */
+    public JoiningInputStream(InputStream first, InputStream second) { 
+       super(first);
+       if (first == null || second == null) {
+           throw new NullPointerException();
+       }
+       this.first = first;
+       this.second = second;
+    }
+
+    /**
+     * wraps read(byte[],int,int) to read a single byte.
+     */
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        if (read(b,0,1) == -1) {
+            return -1;
+        }
+        return b[0] & 0xFF;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+       int c = in.read(b,off,len);
+       if (c == -1 && in == first) {
+           in = second;
+           return in.read(b,off,len);
+       } else {
+           return c;
+       }
+    }
+
+    public void close() throws IOException {
+       first.close();
+       second.close();
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/Journal.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/Journal.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/Journal.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,58 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.text.ParseException;
+
+public class Journal extends AsyncPersistentProps {
+
+    public static final String FILE_PROP = "file";
+    public static final String BYTES_PROP = "bytes";
+
+    File f;
+    RangeSet written;
+
+    public Journal(File f) throws IOException {
+       super(f);
+
+       // Read in the byte ranges.
+       String bytes = getProperty(BYTES_PROP);
+       if (bytes != null) {
+           // try and read existing journal.
+           try {
+               written = RangeSet.parse(bytes);
+           } catch (ParseException e) {
+               throw new IOException("Corrupt journal.");
+           }
+       } else {
+           // new journal.
+           this.written = new RangeSet();
+       }
+
+       // Read in the target file name.
+       String file = getProperty(FILE_PROP);
+       if (file != null) {
+           this.f = new File(file);
+       }
+    }
+
+    public void setTargetFile(File f) {
+       this.f = f;
+       setProperty(FILE_PROP, f.getAbsolutePath());
+    }
+
+    public File getTargetFile() {
+       return f;
+    }
+
+    public void addByteRange(Range r) {
+       written.add(r);
+       setProperty(BYTES_PROP, written.toString());
+    }
+
+    public RangeSet getByteRanges() {
+       return written;
+    }
+}
+
+    

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/JournalingRAF.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/JournalingRAF.java        
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/JournalingRAF.java        
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,81 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+
+public class JournalingRAF extends FilterRAF {
+
+    Journal journal;
+
+    public JournalingRAF(RAF raf, Journal journal) throws IOException {
+       super(raf);
+       if (raf.getMode().equals("r")) {
+           throw new IllegalStateException("Can't create a journal for a "+
+                                           "read-only file.");
+       }
+
+       this.journal = journal;
+       
+       // Track the initial file path.
+       journal.setTargetFile(raf.getFile());
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       super.seekAndWrite(pos,b,off,len);
+       //FIX flush problem, what if it crashes before data is persisted?
+
+       if (journal != null) {
+           // can be null from deleteJournal
+           // Update the journal..
+           journal.addByteRange(new Range(pos,pos+len-1));
+       }
+    }
+
+    public synchronized void renameTo(File newFile) throws IOException {
+       super.renameTo(newFile);
+       // Update the file location.
+
+       // Can be null from deleteJournal()
+       if (journal != null) {
+           // Use raf.getFile() because renameTo() may have failed and was
+           // forced to fall back.
+           journal.setTargetFile(raf.getFile());
+
+           // flush here because it is important that the journal stay in
+           // sync on this operation.
+           journal.flush();
+       }
+    }
+
+    public synchronized void setReadOnly() throws IOException {
+       super.setReadOnly();
+       // done writing, delete the journal
+       deleteJournal();
+    }
+
+    public synchronized void close() throws IOException {
+       super.close();
+       
+       // Can be null from deleteJournal
+       if (journal != null) {
+           journal.close();
+       }
+    }
+    
+    public synchronized void deleteOnClose() {
+       super.deleteOnClose();
+       deleteJournal();
+    }
+
+    private void deleteJournal() {
+       File f = journal.getFile();
+       try {
+           journal.close();
+       } catch (IOException e) {e.printStackTrace();}
+       //FIX maybe throw exception on failed delete?
+       f.delete();
+       journal = null;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/LazyRenameRAF.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/LazyRenameRAF.java        
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/LazyRenameRAF.java        
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,70 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import com.onionnetworks.util.*;
+
+public class LazyRenameRAF extends FilterRAF {
+
+    File destFile;
+
+    public LazyRenameRAF(RAF raf) throws IOException {
+       super(raf);
+       
+       if (getMode().equals("r")) {
+           throw new IllegalStateException("LazyRenameRAFs are only useful "+
+                                           "in read/write mode.");
+       }
+    }
+
+    // setting the destination will not happen until setReadOnly() is called.
+    // It is ok if this throws an IOException because the RAF
+    // will revert to its previous state, no harm done.
+    // document that it will create a temp file in the same directory.
+    /**
+     * If the current location and the newFile are different, it is guarenteed
+     * that the file will be moved to some new location, even if it isn't the
+     * final destination.  This is to allow the safe setting of deleteOnExit
+     * for locations that are intended to be temporary.
+     */
+    public synchronized void renameTo(File newFile) throws IOException {
+       //FIX figure out the proper semantics for this temporary same-directory
+       // file.
+       //
+       // we set the destination before doing anything else, so that if
+       // moving to the new temp location fails, we still have the destination
+       // set.
+       this.destFile = newFile;
+
+       if (getMode().equals("r")) {
+           raf.renameTo(destFile);
+       } else {
+           // create a temp file in the same directory as destFile, if 
+           // destFile is null, then try to create a temp file in the
+           // user temp directory, then fall back to the system temp dir.
+           File newTemp = FileUtil.createTempFile(destFile);
+
+           raf.renameTo(newTemp);   
+       }
+    }
+
+    // This should at least by in read-only mode when it bombs, should
+    // FIX parent.setReadOnly to revert as well.
+    public synchronized void setReadOnly() throws IOException {
+       raf.setReadOnly();
+       if (destFile != null) {
+           raf.renameTo(destFile);
+       }
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/RAF.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/RAF.java  2006-11-10 
20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/RAF.java  2006-11-10 
20:10:52 UTC (rev 10867)
@@ -0,0 +1,199 @@
+package com.onionnetworks.io;
+
+//import org.apache.log4j.Category;
+import java.io.*;
+
+// Implement Filtering.
+public class RAF {
+
+    //  static Category cat = Category.getInstance(RAF.class.getName());
+
+    protected File f;
+    protected String mode;
+    protected RandomAccessFile raf;
+    private boolean closed;
+    protected boolean deleteOnClose;
+
+    public RAF(File f, String mode) throws IOException {
+        this.f = f;
+        this.mode = mode;
+        this.raf = new RandomAccessFile(f,mode);
+    }
+
+    /**
+     * This is only to be used by subclasses requiring more flexible
+     * constructors.
+     */
+    protected RAF() {}
+
+    public File getFile() {
+       return f;
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+        raf.seek(pos);
+        raf.write(b,off,len);
+    }
+
+    public synchronized int seekAndRead(long pos, byte[] b, int off, int len) 
+       throws IOException {
+       raf.seek(pos);
+       return raf.read(b,off,len);
+    }
+
+    public synchronized void seekAndReadFully(long pos, byte[] b, int off,
+                                              int len) throws IOException {
+        raf.seek(pos);
+        raf.readFully(b,off,len);
+    }
+
+    /**
+     * This version of renameTo() attempts to mimic the behavior of the unix 
+     * 'mv' command rather than File.renameTo.  This means that the destFile
+     * will automatically be deleted if it exists and if we are unable to
+     * mv the file directly because they are on different file systems then 
+     * we will manually copy and delete the original.  
+     *
+     * If there is an exception during the copy then we will attempt to 
+     * delete the new copy and revert back to the old copy.  There is a very
+     * slight chance that after reverting, the original copy may be unusable.
+     * This would probably only happen with a file system that is
+     * experiencing IO problems, in which case you are going to have problems
+     * anyway.
+     *
+     * There is also a very rare chance that during a failed copy, we will
+     * be unable to delete a partially written destination file and it will
+     * remain on disk.  This could happen on a directory with "drop box"
+     * semantics where you can only create and write, and not delete.
+     */
+    public synchronized void renameTo(File destFile) throws IOException {
+        if (closed) {
+            throw new IOException("File closed.");
+        }
+        raf.close();
+        // Move to final location.
+        try {
+            // If they are the same file than do nothing.  It is obviously 
+            // important for this to be checked before we delete the destFile.
+            if (f.getCanonicalFile().equals(destFile.getCanonicalFile())) {
+                return;
+            }
+
+            // Delete the destFile if it exists.
+            if (destFile.exists()) {
+                //cat.debug("renameTo(): destFile exists, deleting...");
+                if (!destFile.delete()) {
+                    throw new IOException("Unable to delete destination :"+
+                                          destFile);
+                }
+            }
+            
+            if (!f.renameTo(destFile)) {
+                //cat.debug("renameTo(): File.renameTo failed, copying...");
+                try {
+                    byte[] b = new byte[8192];
+                    InputStream is = new FileInputStream(f);
+                    OutputStream os = new FileOutputStream(destFile);
+                    int c;
+                    while ((c = is.read(b,0,b.length)) != -1) {
+                        os.write(b,0,c);
+                    }
+                    is.close();
+                    os.close();
+
+                   // If there was a prob with the copy then it should 
+                   // have bombed before now.
+                   if (!f.delete()) {
+                       // Even though this is a problem with the source, 
+                       // we still delete the destination and keep the source
+                       // in the catch {} clause because we want this method
+                       // to fall back to the source under any failure
+                       // condition.
+                       throw new IOException("Unable to delete source "+
+                                             "post-move");
+                   }
+                } catch (IOException e) {
+                    if (destFile.exists() && !destFile.delete()) {
+                        throw new IOException("Unable to delete destination "+
+                                              "after failed move : "+destFile+
+                                              " : "+e.getMessage());
+                    }       
+                    throw new IOException("Unable to move "+f+" to "+
+                                          destFile+" : "+e.getMessage());
+                }
+
+                f = destFile;
+            } else {
+                f = destFile;
+            }
+        } finally {
+            // If exception is thrown, re-open the old one.
+            raf = new RandomAccessFile(f,mode);
+        }
+    }
+
+    public synchronized String getMode() {
+        return mode;
+    }
+
+    public synchronized boolean isClosed() {
+       return closed;
+    }
+
+    public synchronized void setReadOnly() throws IOException {
+        if (closed) {
+            throw new IOException("File closed.");
+        }
+        this.mode = "r";
+        raf.close();
+        raf = new RandomAccessFile(f,mode);
+    }
+
+    public synchronized void deleteOnClose() {
+       if (closed) {
+           throw new IllegalStateException("File already closed");
+       }
+       deleteOnClose = true;
+    }
+
+
+    public synchronized void setLength(long len) throws IOException {
+        raf.setLength(len);
+    }
+
+    public synchronized long length() throws IOException {
+       return raf.length();
+    }
+
+    public synchronized void close() throws IOException {
+        closed = true;
+        raf.close();
+       if (deleteOnClose) {
+           if (!f.delete()) {
+               throw new IOException("Unable to delete file on close");
+           }
+       }
+    }
+
+    /**
+     * Cleans up this objects resources by calling close().  This will
+     * also cause the file to be deleted if deleteOnClose() was called.
+     *
+     * @see close()
+     */
+    protected void finalize() throws IOException {
+       if (!closed) {
+           close();
+       }
+    }
+
+    public String toString() {
+       return "RAF[file="+f.getAbsolutePath()+",mode="+mode+"]";
+    }
+}
+
+
+
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/RAFInputStream.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/RAFInputStream.java       
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/RAFInputStream.java       
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,52 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import java.net.*;
+
+public class RAFInputStream extends InputStream {
+
+    RAF raf;
+    long pos;
+
+    public RAFInputStream(RAF raf) { 
+       this.raf = raf;
+    }
+
+    /**
+     * wraps read(byte[],int,int) to read a single byte.
+     */
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        if (read(b,0,1) == -1) {
+            return -1;
+        }
+        return b[0] & 0xFF;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+       if (raf == null) {
+           throw new EOFException();
+       }
+       int c = raf.seekAndRead(pos,b,off,len);
+       if (c >= 0) {
+           pos += c;
+       }
+       return c;
+    }
+
+    public long skip(long n) throws IOException {
+       // don't skip if n < 0
+       if (n > 0) {
+           // don't skip beyond the EOF
+           long result = Math.min(raf.length(),pos+n) - pos;
+           pos += result;
+           return result;
+       }
+       return 0;
+    }
+    
+    // This does not close the underlying RAF.
+    public void close() throws IOException {
+       raf = null;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/RAFOutputStream.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/RAFOutputStream.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/RAFOutputStream.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,34 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import java.net.*;
+
+public class RAFOutputStream extends OutputStream {
+
+    RAF raf;
+    long pos;
+
+    public RAFOutputStream(RAF raf) { 
+       this.raf = raf;
+    }
+
+    /**
+     * wraps write(byte[],int,int) to write a single byte.
+     */
+    public void write(int b) throws IOException {
+       write(new byte[] {(byte) b},0,1);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+       if (raf == null) {
+           throw new EOFException();
+       }
+       raf.seekAndWrite(pos,b,off,len);
+       pos += len;
+    }
+
+    // This does not close the underlying RAF.
+    public void close() throws IOException {
+       raf = null;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/TempRaf.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/TempRaf.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/TempRaf.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,74 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import com.onionnetworks.util.*;
+
+/**
+ * @author Justin Chapweske (justin at chapweske.com)
+ */
+public class TempRaf extends FilterRAF {
+
+    public static final int NEVER = 0;
+    public static final int RENAMED_AND_DONE_WRITING = 1;
+    public static final int RENAMED = 2;
+    public static final int ALWAYS = 3;
+
+    public static final int DEFAULT_KEEP_POLICY = 0;
+
+    int keepPolicy;
+    boolean renamed = false;
+
+    public TempRaf() throws IOException {
+       this(DEFAULT_KEEP_POLICY);
+    }
+
+    public TempRaf(int keepPolicy) throws IOException {
+       // Create a temp file in the user temp dir, or failing that, the
+       // system temp dir.
+       this(new RAF(FileUtil.createTempFile(null),"rw"),keepPolicy);
+    }
+
+    public TempRaf(RAF raf) {
+       this(raf,DEFAULT_KEEP_POLICY);
+    }
+
+    public TempRaf(RAF raf, int keepPolicy) {
+       super(raf);
+       if (keepPolicy != ALWAYS) {
+           // clean up in case of forcable shutdown.
+           raf.getFile().deleteOnExit();
+       }
+
+       this.keepPolicy = keepPolicy;
+    }
+
+    public synchronized void renameTo(File newFile) throws IOException {
+       renamed = true;
+       super.renameTo(newFile);
+    }
+
+    public synchronized void close() throws IOException {
+
+       // keep as a switch statement for readability.
+       switch (keepPolicy) {
+       case ALWAYS:
+           break;
+       case NEVER:
+           deleteOnClose();
+           break;
+       case RENAMED:
+           if (!renamed) {
+               deleteOnClose();
+           }
+           break;
+       case RENAMED_AND_DONE_WRITING:
+           if (!renamed || !getMode().equals("r")) {
+               deleteOnClose();
+           }
+           break;
+       }
+           
+       super.close();
+    }
+
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/io/UnpredictableInputStream.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/io/UnpredictableInputStream.java 
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/io/UnpredictableInputStream.java 
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,56 @@
+package com.onionnetworks.io;
+
+import java.io.*;
+import java.util.Random;
+
+/**
+ * This InputStream is designed to simulate real-world network IO conditions
+ * where less bytes may be returned or skipped than specified.  This is
+ * designed for testing for edge-conditions or incorrect assumptions about IO
+ * semantics.
+ *
+ * @author Justin F. Chapweske
+ */
+public class UnpredictableInputStream extends FilterInputStream {
+
+    Random rand = new Random();
+
+    public UnpredictableInputStream(InputStream is) {
+        super(is);
+    }
+
+    public long skip(long n) throws IOException {
+       // We use nextInt rather than nextLong to ensure equal distribution
+       // across the possible bytes so that we're consistantly skipping
+       // less than n bytes.
+       // (int) cast is safe due to min(int,long)
+       if (rand.nextInt(5) == 0) {
+           // Return 0 length skips quite often for testing implementations.
+           return super.skip(0);
+       }
+       System.out.println("skip!");
+       return super.skip(rand.nextInt((int)Math.min(Integer.MAX_VALUE,n+1)));
+    }
+
+    public int read(byte[] b) throws IOException {
+       // This method must block until data is available, thus we will
+       // keep reading on a 0 byte result.
+       if (b.length == 0) {
+           return 0;
+       }
+       int c;
+       while ((c = read(b,0,b.length)) == 0) {}
+       return c;
+    }
+       
+    public int read(byte[] b, int off, int len) throws IOException {
+       // Even though FilterInputStream's JavaDoc claims that this method
+       // must block until data is available, the current FilterInputStream
+       // implementation doesn't even enforce that.
+       if (rand.nextInt(5) == 0) {
+           // Return 0 length reads quite often for testing implementations.
+           return super.read(b,off,0);
+       }
+       return super.read(b,off,rand.nextInt(len+1));
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/WriteCommitRaf.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/WriteCommitRaf.java       
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/WriteCommitRaf.java       
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,38 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * This raf commits its bytes the first time they are written.
+ *
+ * @author Justin Chapweske
+ */
+public class WriteCommitRaf extends CommitRaf {
+
+    public WriteCommitRaf(RAF raf) {
+       super(raf);
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       super.seekAndWrite(pos,b,off,len);
+       // Allow 0 length write to allow exceptions to be thrown.
+       if (len != 0) {
+           commit(new Range(pos,pos+len-1));
+       }
+    }
+
+    public synchronized void setReadOnly() throws IOException {
+       // When we switch to read-only, we commit the whole file.
+       super.setReadOnly();
+       long fileSize = length();
+       if (fileSize != 0) {
+           commit(new Range(0,fileSize-1));
+       }
+    }
+}
+
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/io/WriteOnceRaf.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/io/WriteOnceRaf.java 
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/io/WriteOnceRaf.java 
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,43 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * This Raf only allows a byte position to be written once.  Any duplicate
+ * bytes are discarded.
+ *
+ * @author Justin Chapweske
+ */
+public class WriteOnceRaf extends WriteCommitRaf {
+
+    // This range set contains all possible unwritten bytes.
+    RangeSet unwritten = new RangeSet().complement();
+
+    public WriteOnceRaf(RAF raf) {
+       super(raf);
+    }
+
+    public synchronized void seekAndWrite(long pos, byte[] b, int off, 
+                                          int len) throws IOException {
+       if (len == 0) {
+           // Do 0 length write to allow exceptions to be thrown.
+           super.seekAndWrite(pos,b,off,len);
+           return;
+       }
+
+       Range r = new Range(pos,pos+len-1);
+       for (Iterator it=unwritten.intersect(new RangeSet(r)).iterator();
+            it.hasNext();) {
+           Range r2 = (Range) it.next();
+           // (int) casts are safe because they will never be larger than len
+           super.seekAndWrite(r2.getMin(),b,off+(int)(r2.getMin()-pos),
+                              (int)r2.size());
+           unwritten.remove(r2);
+       }
+    }
+}
+
+
+

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/net/DatagramSocketFactory.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/net/DatagramSocketFactory.java   
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/net/DatagramSocketFactory.java   
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,24 @@
+// (c) Copyright 2000 Justin F. Chapweske
+// (c) Copyright 2000 Ry4an C. Brase
+
+package com.onionnetworks.net;
+
+import java.net.*;
+import java.io.IOException;
+
+public abstract class DatagramSocketFactory {
+
+    protected DatagramSocketFactory() {}
+
+    public abstract DatagramSocket createDatagramSocket() throws IOException;
+    public abstract DatagramSocket createDatagramSocket(int port) 
+        throws IOException;
+    public abstract DatagramSocket createDatagramSocket(int port, 
+                                                        InetAddress iaddr) 
+        throws IOException;
+    
+
+    public static DatagramSocketFactory getDefault() {
+        return new PlainDatagramSocketFactory();
+    }
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/net/PlainDatagramSocketFactory.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/net/PlainDatagramSocketFactory.java
  2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/net/PlainDatagramSocketFactory.java
  2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,24 @@
+// (c) Copyright 2000 Justin F. Chapweske
+// (c) Copyright 2000 Ry4an C. Brase
+
+package com.onionnetworks.net;
+
+import java.net.*;
+import java.io.*;
+
+public class PlainDatagramSocketFactory extends DatagramSocketFactory {
+
+    public DatagramSocket createDatagramSocket() throws IOException {
+        return new DatagramSocket();
+    }
+
+    public DatagramSocket createDatagramSocket(int port) throws IOException {
+        return new DatagramSocket(port);
+    }
+
+    public DatagramSocket createDatagramSocket(int port, InetAddress iaddr) 
+        throws IOException {
+        
+        return new DatagramSocket(port, iaddr);
+    }
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/util/AsyncPersistentProps.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/util/AsyncPersistentProps.java   
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/util/AsyncPersistentProps.java   
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,153 @@
+package com.onionnetworks.util;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * @author Justin F. Chapweske
+ */
+public class AsyncPersistentProps implements Runnable {
+
+    private File f;
+    private Properties p;
+    private IOException ioe;
+    private boolean closed;
+    private boolean changed, writing;
+
+    /**
+     * Reads in the properties from the file if it exists.  If the file
+     * does not exist then the file and an empty Properties will be created
+     */
+    public AsyncPersistentProps(File f) throws IOException {
+        this.f = f;
+        p = new Properties();
+        if (f.exists()) {
+            p.load(new FileInputStream(f));
+        }
+        new Thread(this,"Props Writer :"+f.getName()).start();
+    }
+
+    public Properties getProperties() {
+        return p;
+    }
+
+    public File getFile() {
+        return f;
+    }
+
+    public synchronized Object setProperty(String key, String value) {
+       checkState();
+
+        Object result = p.setProperty(key,value);
+        changed = true;
+        this.notifyAll();
+        return result;
+    }
+
+    public synchronized Object remove(Object key) {
+       checkState();
+
+        Object result = p.remove(key);
+        if (result != null) {
+            changed = true;
+            this.notifyAll();
+        }
+        return result;
+    }
+
+    public synchronized void clear() {
+       checkState();
+
+        p.clear();
+        changed = true;
+        this.notifyAll();
+    }
+
+    public synchronized String getProperty(String key) {
+        return p.getProperty(key);
+    }
+
+    public synchronized void flush() throws IOException {
+        while (!closed && (changed || writing)) {
+            try {
+                this.wait();
+            } catch (InterruptedException e) {
+                throw new InterruptedIOException(e.getMessage());
+            }
+        }
+
+        if (ioe != null) {
+         /* this code avoids throw {} finally {} for the sake of GCJ 3.0 */
+         IOException ex = ioe;
+         ioe = null;
+         throw ex;
+        }
+    }
+
+    public synchronized void close() throws IOException {
+        flush(); // This will toss the exception if set.
+        closed = true;
+        this.notifyAll();
+    }
+
+    private synchronized void fail(IOException e) {
+       closed = true;
+       ioe = e;
+       this.notifyAll();
+    }
+
+    private void checkState() {
+       if (ioe != null) {
+           throw new IllegalStateException(ioe.getMessage());
+       } else if (closed) {
+            throw new IllegalStateException("Sorry, we're closed");
+       }
+    }
+           
+    public void run() {
+        while (true) {
+            try {
+                byte[] b = null;
+                synchronized (this) {
+                    if (closed) {
+                        return;
+                    }
+                    // If its not changed, then wait.
+                    if (!changed) {
+                        try {
+                            this.wait();
+                        } catch (InterruptedException e) {
+                           fail(new InterruptedIOException(e.getMessage()));
+                       }
+                        if (!changed) {
+                            continue;
+                        } 
+                    }
+                    //snapshot the Properties into a byte[]
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    p.store(baos,null);
+                    b = baos.toByteArray();
+                    changed = false;
+                    writing = true;
+                }
+
+                //Write the snapshot to disk.
+                if (f.exists()) {
+                    f.delete();
+                }
+                FileOutputStream fos = new FileOutputStream(f);
+                fos.write(b);
+                fos.flush();
+                fos.close();
+
+                // Notify that we're done writing.
+                synchronized (this) {
+                    writing = false;
+                    this.notifyAll();
+                }
+            } catch (IOException e) {
+               fail(e);
+            }
+        }
+    }
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/util/BlockDigestInputStream.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/util/BlockDigestInputStream.java 
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/util/BlockDigestInputStream.java 
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,88 @@
+package com.onionnetworks.util;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.security.*;
+
+/**
+ * @author Justin F. Chapweske
+ */
+public class BlockDigestInputStream extends FilterInputStream {
+
+    protected MessageDigest md;
+    protected int blockSize, byteCount;
+    ArrayList digestList = new ArrayList();
+    Buffer[] digests = null;
+
+    public BlockDigestInputStream(InputStream is, String algorithm, 
+                                  int blockSize) 
+        throws NoSuchAlgorithmException {
+        
+        super(is);
+        if (blockSize <= 0) {
+            throw new IllegalArgumentException("blockSize must be > 0");
+        }
+        this.md = MessageDigest.getInstance(algorithm);
+        this.blockSize = blockSize;
+    }
+
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        if (read(b,0,1) == -1) {
+            return -1;
+        }
+        return b[0] & 0xFF;
+    }
+
+    public long skip(long n) throws IOException {
+        byte[] b = new byte[n < 1024 ? (int)n : 1024];
+        long l = n;
+        int c;
+        while (l > 0) {
+            if ((c = read(b, 0, l < 1024 ? (int)l : 1024)) == -1) {
+                break;
+            }
+            l -= c;
+        }
+        return n - l;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        int left = blockSize-byteCount;
+        int c;
+        // truc the read if they want more than is left for this block.
+        if ((c = in.read(b,off,len < left ? len : left)) == -1) {
+            return -1;
+        }
+        md.update(b,off,c);        
+        byteCount += c;
+        // this block is full
+        if(byteCount == blockSize) {
+            digestList.add(new Buffer(md.digest()));        
+            byteCount = 0;
+        }
+        return c;
+    }
+
+    public void finish() {
+        if (byteCount != 0) {
+            digestList.add(new Buffer(md.digest()));
+        }
+        digests = (Buffer[]) digestList.toArray(new Buffer[0]);
+        digestList = null;
+    }
+
+    public void close() throws IOException {
+        if (digestList != null) {
+            finish();
+        }
+        in.close();
+    }
+
+    public Buffer[] getBlockDigests() {
+        if (digests == null) {
+            throw new IllegalStateException("Must call finish or close first");
+        }
+        return digests;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/Buffer.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/Buffer.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/Buffer.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,89 @@
+// (c) Copyright 2000 Justin F. Chapweske
+// (c) Copyright 2000 Ry4an C. Brase
+
+package com.onionnetworks.util;
+
+/**
+ * A class (struct) for holding the offset and length of a byte[] buffer
+ *
+ *@author Justin F. Chapweske
+ */
+public class Buffer {
+
+    public byte[] b;
+    public int off;
+    public int len;
+
+    public Buffer(int len) {
+        this(new byte[len]);
+    }
+
+    public Buffer(byte[] b) {
+        this(b,0,b.length);
+    }
+
+    public Buffer(byte[] b, int off, int len) {
+        if (len < 0 || off < 0 || off+len > b.length) {
+            throw new ArrayIndexOutOfBoundsException("b.length="+b.length+
+                                                     ",off="+off+",len="+len);
+        }
+
+        this.b = b;
+        this.off = off;
+        this.len = len;
+    }
+
+    /**
+     *  Calling this defeats the purpose of reusing byte arrays, so only
+     *  call this when you have to get a byte[].
+     */
+    public byte[] getBytes() {
+       byte[] retval = new byte[len];
+       System.arraycopy(b, off, retval, 0, len);
+       return retval;
+    }
+    
+    /**
+     * If two buffers are the same length and contain the same data, then this
+     * method will return true, otherwise false.
+     */
+    public boolean equals(Object o) {
+        if (o instanceof Buffer) {
+            Buffer buf = (Buffer) o;
+            if (buf.len != len) {
+                return false;
+            }
+            for (int i=0;i<len;i++) {
+                if (buf.b[buf.off+i] != b[off+i]) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Uncomment this if you're not a coward.
+     * /
+    public int hashCode() {
+        int retval = 0;
+        for (int i=0;i<len;i++) {
+            retval |= ((int)(b[off+i])) << (8 * (i & 4));
+        }
+        return retval;
+    }
+    */
+
+    public String toString() {
+      StringBuffer rep = new StringBuffer("Buffer{length: "+len+"; offset: 
"+off+"; ");
+      for (int i = off; i<len; i++) {
+       rep.append(""+i+": "+b[i]);
+       if (i != len-1) rep.append(", ");
+      }
+      rep.append("}");
+      return rep.toString();
+    }
+
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionEvent.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionEvent.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionEvent.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,31 @@
+package com.onionnetworks.util;
+
+import java.util.EventObject;
+
+/**
+ * This class encapsulates an Exception and a Source to be dispatched
+ * through the ExceptionHandler class.
+ *
+ * @author Justin Chapweske
+ */
+public class ExceptionEvent extends EventObject {
+
+    Throwable t;
+
+    /**
+     * @param source The source of the event.  This should probably be
+     * the object that caught the exception.
+     * @param t The Throwable that is being fired.
+     */
+    public ExceptionEvent(Object source, Throwable t) {
+       super(source);
+       this.t = t;
+    }
+
+    /**
+     * @return The Throwable being dispatched.
+     */
+    public Throwable getException() {
+       return t;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionHandler.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionHandler.java   
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/ExceptionHandler.java   
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,17 @@
+package com.onionnetworks.util;
+
+import java.util.EventListener;
+
+/**
+ * An interface for an Exception handler.
+ *
+ * @author Justin F. Chapweske
+ */
+public interface ExceptionHandler extends EventListener {
+
+    public static final String HANDLE_EXCEPTION = "handleException";
+    
+    public static final String[] EVENTS = new String[] { HANDLE_EXCEPTION };
+
+    public void handleException(ExceptionEvent ev);
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrity.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrity.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrity.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,47 @@
+package com.onionnetworks.util;
+
+/**
+ * This interface provides access to a number of block hashes for a file.  
+ * These hashes can be used to check the integrity of a single block and the
+ * overall file.
+ */
+public interface FileIntegrity {
+
+    /**
+     * @return the message digest algorithm used to create the the hashes.
+     */
+    public String getAlgorithm();
+
+    /**
+     * Specifies the size of each block.  This size should be a power of 2
+     * and all blocks will be this size, expect for the last block which may
+     * be equal to or less than the block size (but not 0).
+     *
+     * @return the block size.
+     */
+    public int getBlockSize();
+
+    /**
+     * @return the size of the file.
+     */
+    public long getFileSize();
+
+    /**
+     * Returns the number of blocks that make up the file.  This value will
+     * be equal to ceil(fileSize/blockSize).
+     *
+     * @return the block count.
+     */
+    public int getBlockCount();
+
+    /**
+     * @return the hash of the specified block.
+     */
+    public Buffer getBlockHash(int blockNum);
+
+    /**
+     * @return the hash of the entire file.
+     */
+    public Buffer getFileHash();
+
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrityImpl.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrityImpl.java  
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/FileIntegrityImpl.java  
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,95 @@
+package com.onionnetworks.util;
+
+/**
+ * This class provides a way to access various structures needed to check
+ * the integrity of a file.
+ *
+ * @author Justin F. Chapweske
+ */
+public class FileIntegrityImpl implements FileIntegrity {
+
+    private String algo;
+    private Buffer fileHash;
+    private Buffer[] blockHashes;
+    private int blockSize, blockCount;
+    private long fileSize;
+    
+
+    public FileIntegrityImpl(String algorithm, Buffer fileHash, 
+                             Buffer[] blockHashes, long fileSize, 
+                             int blockSize) {
+        if (algorithm == null) {
+            throw new NullPointerException("algorithm is null");
+        } else if (fileHash == null) {
+            throw new NullPointerException("fileHash is null");
+        } else if (blockHashes == null) {
+            throw new NullPointerException("blockHashes are null");
+        } else if (fileSize < 0) {
+            throw new IllegalArgumentException("fileSize < 0");
+        } else if (blockSize < 0) {
+            throw new IllegalArgumentException("blockSize < 0");
+        }
+        this.algo = algorithm;
+        this.fileHash = fileHash;
+        this.blockHashes = blockHashes;
+        this.fileSize = fileSize;
+        this.blockSize = blockSize;
+        this.blockCount = Util.divideCeil(fileSize,blockSize);
+        if (blockHashes.length != blockCount) {
+            throw new IllegalArgumentException("Incorrect block hash count");
+        }
+    }
+
+    /**
+     * @return the message digest algorithm used to create the the hashes.
+     */
+    public String getAlgorithm() {
+        return algo;
+    }
+
+    /**
+     * Specifies the size of each block.  This size should be a power of 2
+     * and all blocks will be this size, expect for the last block which may
+     * be equal to or less than the block size (but not 0).
+     *
+     * @return the block size.
+     */
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * @return the size of the file.
+     */
+    public long getFileSize() {
+        return fileSize;
+    }
+        
+    /**
+     * Returns the number of blocks that make up the file.  This value will
+     * be equal to ceil(fileSize/blockSize).
+     *
+     * @return the block count.
+     */
+    public int getBlockCount() {
+        return blockCount;
+    }
+ 
+
+    /**
+     * @return the hash of the specified block.
+     */
+    public Buffer getBlockHash(int blockNum) {
+        if (blockNum < 0 || blockNum >= blockCount) {
+            throw new IllegalArgumentException("Invalide block #"+blockNum);
+        }
+        return blockHashes[blockNum];
+    }
+
+    /**
+     * @return the hash of the entire file.
+     */
+    public Buffer getFileHash() {
+        return fileHash;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/FileUtil.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/FileUtil.java   
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/FileUtil.java   
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,217 @@
+package com.onionnetworks.util;
+
+import java.util.BitSet;
+import java.util.StringTokenizer;
+import java.net.URL;
+import java.io.*;
+
+public class FileUtil {
+    
+    // safe characters for file name sanitization
+    static BitSet safeChars = new BitSet(256);
+
+    static {
+       // a-z
+       for (int i='a';i<='z';i++) {
+           safeChars.set(i);
+       }
+       // A-Z
+       for (int i='A';i<='Z';i++) {
+           safeChars.set(i);
+       }
+       // 0-9
+       for (int i='0';i<='9';i++) {
+           safeChars.set(i);
+       }
+       safeChars.set('-');
+       safeChars.set('_');
+       safeChars.set(' ');
+       safeChars.set('.');
+    }
+
+    public static final String sanitizeFileName(String name) {
+       StringBuffer result = new StringBuffer();
+       for (int i=0;i<name.length();i++) {
+           char c = name.charAt(i);
+           // squish multiple '.'s into a single one.
+           if (c == '.' && i < name.length()-1 && name.charAt(i+1) == '.') {
+               continue;
+           }
+           if (safeChars.get(c)) {
+               result.append(c);
+           }
+       }
+       return result.toString();
+    }
+
+    public static final String pickSafeFileName(URL url) {
+       String name = sanitizeFileName(new File(url.getFile()).getName());
+       if (name == null || name.equals("")) {
+           name = "index.html";
+       }
+       return name;
+    }
+
+    /**
+     * Creates a file in the .onion directory after scrubbing every directory
+     * and filename for unsafe chracters.
+     * @param rel the relative path to clean and put in .onion
+     * @return File object that's name safe
+     * @throws IllegalArgumentException if path isn't relative or if any path 
+     *  element (including the filename) is "" after sanitization.
+     */
+    public static File safeOnionFile(String rel) {
+        if ((new File(rel)).isAbsolute()) {
+            throw new IllegalArgumentException(rel + " isn't relative");
+        }
+        StringBuffer safe = new StringBuffer();
+        for (StringTokenizer st = new StringTokenizer(rel, File.separator);
+                st.hasMoreTokens(); ) {
+            String tok = sanitizeFileName(st.nextToken());
+            if (tok == null || "".equals(tok)) {
+                throw new IllegalArgumentException("collapsed elemnt");
+            }
+            if (safe == null) {
+                safe = new StringBuffer(tok);
+            } else {
+                safe.append(File.separator).append(tok);
+            }
+        }
+
+       File result = new File(getOnionDir(), safe.toString()); 
+
+       try {
+           ensureExists(result);
+       } catch (IOException e) {
+           throw new IllegalStateException
+               ("Failed to ensure that file exists: "+e.getMessage());
+       }
+
+       return result;
+    }
+    
+    public static void ensureExists(File f) throws IOException {
+       if (!f.getParentFile().exists()) {
+           if (!f.getParentFile().mkdirs()) {
+               throw new IOException("Couldn't create parent dirs: "+f);
+           }
+       }
+       
+       if (!f.exists()) {
+           f.createNewFile();
+       }
+    }
+
+    /**
+     * Get the .onion directory off of the user's home directory.
+     * Created if it doesn't exist
+     * @return File to onion networks directory, null on failure to create
+     */
+    public static File getOnionDir() {
+       String s = System.getProperty("user.home");
+       // If they don't have a home directory, then null ("java.io.tmpdir")
+       // will be used.
+       if (s == null) {
+           //System.out.println("user.home was null");
+           return null;
+       }
+       File f = new File(s, ".onion"); // FIX hardcoded name.
+       //System.out.println(f);
+       // check if exists
+       if (!f.exists()) {
+           //System.out.println("doesn't exist");
+           // make dirs
+           if (!f.mkdirs()) {
+               //System.out.println("mkdirs failed");
+               // fall back to system temp dir.
+               return null;
+           }
+       }
+       return f;
+    }
+
+    /**
+     * Create a temp directory off of the onion directory, we do this
+     * instead of the system temp directory because the user probably has more
+     * disk space in their home directory.
+     */
+    public static final File getUserTempDir() {
+       
+       File f = new File(getOnionDir(), "tmp"); // FIX hardcoded name
+       //System.out.println(f);
+       // check if exists
+       if (!f.exists()) {
+           //System.out.println("doesn't exist");
+           // make dirs
+           if (!f.mkdirs()) {
+               //System.out.println("mkdirs failed");
+               // fall back to system temp dir.
+               return null;
+           }
+       }
+       return f;
+    }
+
+    /**
+     * Create a temporary file in the same directory as f and with a similar
+     * name.  If f is null, then a randomly named file will be created in
+     * either the user temp directory or the system temp directory.
+     */
+    public static final File createTempFile(File f) throws IOException {
+       //FIX unit test for relative file
+       //FIX the file names are hardcoded
+       File parent;
+       String name;
+       if (f == null) {
+           // null parent causes it to use the system temp dir.
+           parent = getUserTempDir();
+           name = "onion";
+       } else {
+           parent = f.getAbsoluteFile().getParentFile();
+           name = f.getName();
+           // createTempFile requires a suffix length of at least 3
+           if (name.length() < 3) {
+               name += "onion";
+           }
+       }
+       
+       try {
+           return File.createTempFile(name,null,parent);
+       } catch (IOException e) {
+           if (f != null) {
+               // try the user's temp dir
+               return createTempFile(null);
+           } else if (parent != null) {
+               // try the system temp dir
+               return File.createTempFile(name,null,null);
+           } else {
+               throw new IOException("Unable to create temp file");
+           }
+       }
+    }
+
+    public static void skipFully(InputStream is, long count) 
+       throws IOException {
+       
+       byte[] b = null;
+
+       long left = count;
+       while (left > 0) {
+           long skipped = is.skip(left);
+           if (skipped == 0) {
+               // We couldn't skip any bytes, lets try reading some.
+               if (b == null) {
+                   b = new byte[1024];
+               }
+               // (int) cast is safe due to min(int,long)
+               int c = is.read(b,0,(int)Math.min(b.length,left));
+               if (c == -1) {
+                   throw new EOFException();
+               } else {
+                   skipped = c;
+               }
+           }
+           left -= skipped;
+       }
+    }
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/util/FilteringIterator.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/FilteringIterator.java  
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/FilteringIterator.java  
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,115 @@
+package com.onionnetworks.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/** FilteredIterators wrap Iterators and return only elements that pass
+ * a provided test.  No more than one value is buffered up.
+ * ConcurrentModificationExceptions and other exceptions filter up correctly.
+ * remove() is not supported.  This implemention isn't synchronized.  The
+ * easiest way to synchronize is shown in the example.
+ *
+ * <p>Example:
+ * <pre>
+    Iterator i = Arrays.asList(new String[] {"a",null,"was",null}).iterator();
+    Iterator f = new FilteringIterator(i) {
+       public boolean accept(Object o) {
+           return (o != null);
+       }
+       // uncomment the next to lines if you want it synchronized
+       // public synchronized Object next() { return super.next(); }
+       // public synchronized boolean hasNext() { return super.hasNext(); }
+    });
+    for (; f.hasNext(); ) {
+       Sytem.out.println("non-null: " + f.next());
+    }
+   </pre>
+ * @author Ry4an
+ */
+public abstract class FilteringIterator implements Iterator {
+
+    private Iterator parent;
+    private Object next;
+    private boolean removeOkay = true;
+
+    /** Create a FilteringIterator and provide the parent Iterator
+     * @param Iterator the iterator to wrap w/ the filter
+     */
+    public FilteringIterator(Iterator p) {
+       parent = p;
+    }
+
+    /** Unsupported.
+     */
+    public void remove() {
+       throw new UnsupportedOperationException();
+    }
+
+    /** Checks if the parent iterator has another element that will
+     * pass the filter defined in <code>accept</code>.
+     * @return true = another passing object available, false otherwise
+     * @see accept
+     */
+    public boolean hasNext() {
+       while ((next == null) && (parent.hasNext())) {
+           Object o = parent.next();
+           if (accept(o)) {
+               next = o;
+               return true;
+           }
+       }
+       return (next != null);
+    }
+    
+    /** Fill in this method with a test that will be applied to 
+     * each object which is a candidate for passing through the filter.
+     * @param o the object which may be passed through the filter
+     * @return true indciated the object should be returned. else false.
+     */
+    protected abstract boolean accept(Object o);
+    
+    /** Returns the next object from the parent iterator which passes the
+     * filter defined by <code>accept</code>.
+     * @return Object an object which passes <code>accept</code>
+     * @see accept
+     */
+    public Object next() {
+       if (! hasNext()) {
+           throw new NoSuchElementException();
+       }
+       Object retval = next;
+       next = null;
+       return retval;
+    }
+
+    /** Test and example. */
+    public static void main(String[] args) {
+       java.util.List l = new java.util.LinkedList(java.util.Arrays.asList
+           (new String[] {"a",null,"was",null})); // the test array
+       Iterator i = l.iterator();
+       Iterator f = new FilteringIterator(i) {
+           public boolean accept(Object o) {
+               return (o != null);
+           }
+       };
+       System.out.println("--[ Unfiltered list: ]--");
+       for(Iterator j=l.iterator(); j.hasNext(); ) {
+           System.out.println("Item: " + j.next());
+       }
+       System.out.println("--[ List with null filter: ]--");
+       try { // note: this test code is dependent on the test array
+           Object o;
+           if (! f.hasNext()) { throw new Exception(); }
+           if (! (o=f.next()).equals("a")) { throw new Exception(); }
+           System.out.println("Item: " + o);
+           if (! (o=f.next()).equals("was")) { throw new Exception(); }
+           System.out.println("Item: " + o);
+           if (f.hasNext()) { throw new Exception(); }
+       } catch (Throwable t) {
+           System.err.println("Something unexpected happened:");
+           t.printStackTrace();
+       }
+    }
+}
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/IntIterator.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/IntIterator.java        
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/IntIterator.java        
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,10 @@
+package com.onionnetworks.util;
+
+/**
+ * @author Justin F. Chapweske
+ */
+public interface IntIterator {
+    public boolean hasNextInt();
+    public int nextInt();
+    public void removeInt();
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/InvokeEvent.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/InvokeEvent.java        
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/InvokeEvent.java        
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,15 @@
+package com.onionnetworks.util;
+
+import java.util.EventObject;
+
+public class InvokeEvent extends EventObject {
+    Runnable r;
+    public InvokeEvent(Object source, Runnable r) {
+       super(source);
+       this.r = r;
+    }
+    
+    public Runnable getRunnable() {
+       return r;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/InvokingDispatch.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/InvokingDispatch.java   
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/InvokingDispatch.java   
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,48 @@
+package com.onionnetworks.util;
+
+import java.util.*;
+
+public class InvokingDispatch extends ReflectiveEventDispatch implements
+    EventListener{
+
+    public static final String INVOKE = "invoke";
+
+    public InvokingDispatch() {
+       super();
+       addListener(this,this,INVOKE);
+    }
+    
+    // can't be inner class unless we create a public interface for invoke()
+    public void invoke(InvokeEvent ev) {
+       ev.getRunnable().run();
+       synchronized (ev) {
+           ev.notifyAll();
+       }
+    }
+
+    public void invokeLater(Runnable r) {
+       fire(new InvokeEvent(this,r),INVOKE);
+    }
+
+    public void invokeAndWait(Runnable r) throws InterruptedException {
+       InvokeEvent ev = new InvokeEvent(this,r);
+       synchronized (ev) {
+           fire(ev,INVOKE);
+           ev.wait();
+       }
+    }
+
+    public class InvokeEvent extends EventObject {
+       Runnable r;
+       public InvokeEvent(Object source, Runnable r) {
+           super(source);
+           this.r = r;
+       }
+       
+       public Runnable getRunnable() {
+           return r;
+       }
+    }
+}
+
+

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/JoiningIterator.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/JoiningIterator.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/JoiningIterator.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,25 @@
+package com.onionnetworks.util;
+
+import java.util.Iterator;
+
+/**
+ * @author Ry4an Brase
+ */
+public class JoiningIterator implements Iterator {
+    private Iterator first, second;
+    public JoiningIterator(Iterator f, Iterator s) {
+        first = f; second = s;
+    }
+
+    public boolean hasNext() {
+        return first.hasNext() || second.hasNext();
+    }
+
+    public Object next() {
+        return (first.hasNext())?first.next():second.next(); // throws NSEEx
+    }
+
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/NativeDeployer.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/NativeDeployer.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/NativeDeployer.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,152 @@
+package com.onionnetworks.util;
+
+import java.io.*;
+import java.util.*;
+import java.net.URL;
+
+/**
+ * This class is used for deploying native libraries that are stored inside
+ * jar files.
+ *
+ * For each jar that contains native libraries there must be a file called 
+ * "lib/native.properties" that has a format similar to the following:
+ * <pre>
+ * 
com.onionnetworks.native.keys=fec8-linux-x86,fec16-linux-x86,fec8-win32,fec16-win32
+ *
+ * com.onionnetworks.native.fec8-linux-x86.name=fec8
+ * com.onionnetworks.native.fec8-linux-x86.osarch=linux-x86
+ * com.onionnetworks.native.fec8-linux-x86.path=lib/linux/x86/libfec8.so
+ *
+ * com.onionnetworks.native.fec16-linux-x86.name=fec16
+ * com.onionnetworks.native.fec16-linux-x86.osarch=linux-x86
+ * com.onionnetworks.native.fec16-linux-x86.path=lib/linux/x86/libfec16.so
+ * 
+ * com.onionnetworks.native.fec8-win32.name=fec8
+ * com.onionnetworks.native.fec8-win32.osarch=win32
+ * com.onionnetworks.native.fec8-win32.path=lib/win32/fec8.dll
+ * 
+ * com.onionnetworks.native.fec16-win32.name=fec16
+ * com.onionnetworks.native.fec16-win32.osarch=win32
+ * com.onionnetworks.native.fec16-win32.path=lib/win32/fec16.dll
+ * </pre>
+ * 
+ *
+ * For the "osarch" property note that Sun's VM uses 'i386' and IBM's uses 
+ * 'x86' so we convert all to 'x86'.
+ * For now, we map 'Windows 95', 'Windows 98', 'Windows NT', and 
+ * 'Windows 2000', no matter what the architecture, all to 'win32'. 
+ * Depending on what native libraries are added in the future, this may have 
+ * to be made more flexible; for example, if a library depends on Windows 2000
+ * features or is tuned for Pentium processors.
+ * We will just the "os.name" and "os.arch" properties to retrieve this 
+ * information on all other systems not explicitly mentioned above.
+ *
+ *
+ * @author Justin F. Chapweske
+ *
+ */
+public class NativeDeployer {
+
+
+    public final static String OS_ARCH =
+       (System.getProperty("os.name").startsWith("Windows ")) ? "win32" :
+        System.getProperty("os.name").toLowerCase()+"-"+
+        (System.getProperty("os.arch").toLowerCase().indexOf("86") != -1 ?
+         "x86" : System.getProperty("os.arch").toLowerCase());
+
+    public final static String NATIVE_PROPERTIES_PATH = 
+        "lib/native.properties";
+
+    public synchronized final static String getLibraryPath(ClassLoader cl, 
+                                                           String libName) {
+        long t = System.currentTimeMillis();
+       IOException iox = null;
+       /* this code avoids try {} finally {} idiom for the sake of GCJ 3.0 */
+       try {
+           String libPath = (String) findLibraries(cl).get(libName);
+           if (libPath == null) {
+               return null;
+           }
+            try {
+                return getLocalResourcePath(cl, libPath);
+           } catch (IOException ex) {
+             iox = ex;
+            } 
+           System.out.println("It took "+(System.currentTimeMillis()-t)+
+                              " millis to extract "+libName);
+           if (iox != null) {
+             iox.printStackTrace();
+           }
+           
+       } catch (IOException e) {
+           e.printStackTrace();
+       } 
+       return null;
+    }
+
+    // Since the local copy of the resource isn't stored in a temporary
+    // directory, at some point we'll presumably implement version-based
+    // caching rather than extracting the file every time the code is run....
+    public synchronized final static String getLocalResourcePath
+        (ClassLoader cl, String resourcePath) throws IOException {
+
+        File f = new File(System.getProperty("user.home")+
+                          File.separator+".onionnetworks"+File.separator+
+                          resourcePath);
+        File parentF = f.getParentFile();
+        if (parentF == null) {
+            return null;
+        }
+        if (!parentF.exists()) {
+            parentF.mkdirs();
+        }
+        URL url = cl.getResource(resourcePath);
+        if (url == null) {
+            return null;
+        }
+        InputStream is = url.openStream();
+        f.delete(); // VERY VERY important, VM crashes w/o this :P
+        OutputStream os = new FileOutputStream(f);
+        
+        byte[] b = new byte[1024];
+        int c;
+        while ((c = is.read(b)) != -1) {
+            os.write(b,0,c);
+        }
+        is.close();
+        os.flush();
+        os.close();
+        return f.toString();
+    }
+
+    /**
+     * @return A HashMap mapping library names to paths for this os/arch.
+     */
+    private final static HashMap findLibraries(ClassLoader cl)
+        throws IOException {
+       
+        HashMap libMap = new HashMap();
+       // loop through all of the properties files.
+       for (Enumeration en=cl.getResources(NATIVE_PROPERTIES_PATH);
+            en.hasMoreElements();){
+           Properties p = new Properties();
+           p.load(((URL) en.nextElement()).openStream());
+           // Extract the keys and loop through all of the libs.
+           for (StringTokenizer st = new StringTokenizer
+                (p.getProperty("com.onionnetworks.native.keys"),",");
+                st.hasMoreTokens();) {
+                String key = st.nextToken().trim();
+                // If it matches the os and arch then add it.
+                if (p.getProperty("com.onionnetworks.native."+key+".osarch").
+                    trim().equals(OS_ARCH)) {
+
+                    libMap.put(p.getProperty
+                               
("com.onionnetworks.native."+key+".name").trim(),
+                               p.getProperty
+                               
("com.onionnetworks.native."+key+".path").trim());
+                }
+            }
+       }
+       return libMap;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/NetUtil.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/NetUtil.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/NetUtil.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,102 @@
+// (c) Copyright 2000 Justin F. Chapweske
+// (c) Copyright 2000 Ry4an C. Brase
+
+package com.onionnetworks.util;
+
+import java.net.URL;
+import java.net.InetAddress;
+import java.io.IOException;
+
+public class NetUtil {
+
+    /**
+     * Takes a URL whose host portion is a name and returns a list of URLs with
+     * all the different IP addreses for theat host name found by
+     * InetAddress.getAllByName
+     *
+     * @param url the url for which to find other locations
+     * @return urls w/ all the IPs that DNS maps to the given URL's hostname
+     * @author Ry4an Brase (ry4an at onionnetworks.com)
+     */
+    public static final URL[] getIpUrlsByName(URL url) throws IOException {
+
+        //String query = url.getQuery();   // These three are redundant
+        //String path = url.getPath();
+        //String authority = url.getAuthority();
+        String userInfo = url.getUserInfo();
+        String protocol = url.getProtocol();
+        String host = url.getHost();
+        String file = url.getFile();
+        String ref = url.getRef();
+        int port = url.getPort();
+
+        //System.out.println("Query = '" + query + "'");
+        //System.out.println("Path = '" + path + "'");
+        //System.out.println("Authority = '" + authority + "'");
+        //System.out.println("UserInfo = '" + userInfo + "'");
+        //System.out.println("Protocol = '" + protocol + "'");
+        //System.out.println("Host = '" + host + "'");
+        //System.out.println("File = '" + file + "'");
+        //System.out.println("Ref = '" + ref + "'");
+        //System.out.println("Port = " + port);
+
+        if (host == null || "".equals(host)) { // avoids UnknownHostException
+            return new URL[] { url };
+        }
+
+        InetAddress[] addrs = InetAddress.getAllByName(host);
+        URL[] retval = new URL[addrs.length];
+        for (int i=0; i < addrs.length; i++) {
+            retval[i] = new URL(
+                protocol,
+                ((userInfo == null)
+                    ? addrs[i].getHostAddress()
+                    : (userInfo + "@" + addrs[i].getHostAddress())),
+                port, // -1 is okay
+                ((ref == null)
+                    ? file
+                    : (file + "#" + ref)));
+        }
+
+        return retval;
+
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        if (args.length == 0) {
+            args = new String[] {
+
+                // Everything
+                "http://user:pass at 
cnn.com:14234/dir/foobar?param&adsf=2312#anc",
+
+                // no ref
+                "http://user:pass at cnn.com:14234/dir/foobar?param&adsf=2312",
+
+                // user w/ no pass
+                "http://user at cnn.com:14234/dir/foobar?param&adsf=2312#anc",
+
+                // no user or pass
+                "http://cnn.com:14234/dir/foobar?param&adsf=2312#anc";,
+
+                // no host
+                "http:/dir/foobar?param&adsf=2312#anc",
+
+                // bare bones
+                "http://cnn.com/";,
+
+                // IP for the host makes this useless but harmless
+                "http://64.236.24.4:14234/dir/foobar";,
+            };
+        }
+        
+        for (int j = 0; j < args.length; j++) {
+            System.out.println("----------[ Matches for: " + args[j]);
+            URL[] urls = getIpUrlsByName(new URL(args[j]));
+
+            for (int i=0; i < urls.length; i++) {
+                System.out.println(urls[i].toExternalForm());
+            }
+        }
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/Range.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/Range.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/Range.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,216 @@
+package com.onionnetworks.util;
+
+import java.text.ParseException;
+
+/**
+ * This class represents a range of integers (incuding positive and negative 
+ * infinity).
+ */
+public class Range {
+
+    private boolean negInf, posInf;
+    private long min,max;
+   
+    /**
+     * Creates a new Range that is only one number, both min and max will
+     * equal that number.
+     * @param num The number that this range will encompass.
+     */
+    public Range(long num) {
+        this(num,num,false,false);
+    }
+
+    /**
+     * Creates a new Range from min and max (inclusive)
+     * @param min The min value of the range.
+     * @param max The max value of the range.
+     */
+    public Range(long min, long max) {
+        this(min,max,false,false);
+    }
+
+    /**
+     * Creates a new Range from min to postive infinity
+     * @param min The min value of the range.
+     * @param posInf Must be true to specify max == positive infinity
+     */
+    public Range(long min, boolean posInf) {
+        this(min,Long.MAX_VALUE,false,posInf);
+        if (!posInf) {
+            throw new IllegalArgumentException("posInf must be true");
+        }
+    }
+
+    /**
+     * Creates a new Range from negative infinity to max.
+     * @param negInf Must be true to specify min == negative infinity
+     * @param max The max value of the range.
+     */
+    public Range(boolean negInf, long max) {
+        this(Long.MIN_VALUE,max,negInf,false);
+        if (!negInf) {
+            throw new IllegalArgumentException("negInf must be true");
+        }
+    }
+
+    /**
+     * Creates a new Range from negative infinity to positive infinity.
+     * @param negInf must be true.
+     * @param posInf must be true.
+     */
+    public Range(boolean negInf, boolean posInf) {
+        this(Long.MIN_VALUE,Long.MAX_VALUE,negInf,posInf);
+        if (!negInf || !posInf) {
+            throw new IllegalArgumentException
+                ("negInf && posInf must be true");
+        }
+    }
+
+    private Range(long min, long max, boolean negInf, boolean posInf) {
+       if (min > max) {
+           throw new IllegalArgumentException
+             ("min cannot be greater than max");
+       }
+       // very common bug, its worth reporting for now.
+       if (min == 0 && max == 0) {
+           System.err.println("Range.debug: 0-0 range detected. "+
+                              "Did you intend to this? :");
+           new Exception().printStackTrace();
+       }
+        this.min = min;
+        this.max = max;
+        this.negInf = negInf;
+        this.posInf = posInf;
+    }
+    
+    /**
+     * @return true if min is negative infinity.
+     */
+    public boolean isMinNegInf() {
+        return negInf;
+    }
+
+    /**
+     * @return true if max is positive infinity.
+     */
+    public boolean isMaxPosInf() {
+        return posInf;
+    }
+
+    /**
+     * @return the min value of the range.
+     */
+    public long getMin() {
+       return min;
+    }
+    
+    /**
+     * @return the max value of the range.
+     */
+    public long getMax() {
+       return max;
+    }
+    
+    /**
+     * @return The size of the range (min and max inclusive) or -1 if the range
+     * is infinitly long.
+     */
+    public long size() {
+        if (negInf || posInf) {
+            return -1;
+        }
+        return max-min+1;
+    }
+
+    /**
+     * @param i The integer to check to see if it is in the range.
+     * @return true if i is in the range (min and max inclusive)
+     */
+    public boolean contains(long i) {
+       return i >= min && i <= max;
+    }
+    
+    /**
+     * @param r The range to check to see if it is in this range.
+     * @return true if this range contains the entirety of the passed range.
+     */
+    public boolean contains(Range r) {
+       return r.min >= min && r.max <= max;
+    }
+    
+    
+    public int hashCode() {
+       return (int) (min + 23 * max);
+    }
+    
+    public boolean equals(Object obj) {
+       if (obj instanceof Range &&
+           ((Range) obj).min == min && ((Range) obj).max == max &&
+            ((Range) obj).negInf == negInf && ((Range) obj).posInf == posInf) {
+           return true;
+       } else {
+           return false;
+       }
+    }
+    
+    public String toString() {
+       if (!negInf && !posInf && min == max) {
+           return new Long(min).toString();
+       } else {
+           return (negInf ? "(" : ""+min) + "-" + (posInf ? ")" : ""+max);
+       }
+    }
+    
+    /**
+     * This method creates a new range from a String.
+     * Allowable characters are all integer values, "-", ")", and "(".  The
+     * open and closed parens indicate positive and negative infinity.
+     * <pre>
+     * Example strings would be:
+     * "11" is the range that only includes 11
+     * "-6" is the range that only includes -6
+     * "10-20" is the range 10 through 20 (inclusive)
+     * "-10--5" is the range -10 through -5
+     * "(-20" is the range negative infinity through 20
+     * "30-)" is the range 30 through positive infinity.
+     * </pre>
+     * @param s The String to parse
+     * @return The resulting range
+     * @throws ParseException, 
+     */
+    public static final Range parse(String s) throws ParseException {
+        try {
+            long min=0,max=0;
+            boolean negInf=false,posInf=false;
+            // search from the 1 pos because it may be a negative number.
+            int dashPos = s.indexOf("-",1);
+            if (dashPos == -1) { // no dash, one value.
+                min = max = Long.parseLong(s);
+            } else {
+                if (s.indexOf("(") != -1) {
+                    negInf = true;
+                } else {
+                    min = Long.parseLong(s.substring(0,dashPos));
+                }
+                if (s.indexOf(")") != -1) {
+                    posInf = true;
+                } else {
+                    max = Long.parseLong(s.substring(dashPos+1,s.length()));
+                }
+            }
+            if (negInf) {
+                if (posInf) {
+                    return new Range(true,true);
+                } else {
+                    return new Range(true,max);
+                }
+            } else if (posInf) {
+                return new Range(min,true);
+            } else {
+                return new Range(min,max);
+            }
+        } catch (RuntimeException e) {
+            throw new ParseException(e.getMessage(),-1);
+        }
+    }    
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/RangeSet.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/RangeSet.java   
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/RangeSet.java   
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,428 @@
+package com.onionnetworks.util;
+
+import java.util.*;
+import java.io.*;
+import java.text.ParseException;
+
+/**
+ * This class represents a set of integers in a compact form by using ranges.
+ * This is essentially equivilent to run length encoding of a bitmap-based
+ * set and thus works very well for sets with long runs of integers, but is
+ * quite poor for sets with very short runs.
+ *
+ * This class is similar in flavor to Perl's IntSpan at 
+ * http://world.std.com/~swmcd/steven/perl/lib/Set/IntSpan/
+ *
+ * The Ranges use negative and positive infinity so there should be no
+ * border issues with standard set operations and they should behave
+ * exactly as you'd expect from your set identities.
+ *
+ * While the data structure itself should be quite efficient for its intended
+ * use, the actual implementation could be heavily optimized beyond what I've 
+ * done, feel free to improve it.
+ *
+ * @author Justin F. Chapweske
+ */
+public class RangeSet {
+ 
+    public static final int DEFAULT_CAPACITY = 16;
+    
+    boolean posInf, negInf;
+    int rangeCount;
+    long[] ranges;
+    
+    /**
+     * Creates a new empty RangeSet
+     */
+    public RangeSet() {
+       ranges = new long[DEFAULT_CAPACITY * 2];
+    }
+
+    /**
+     * Creates a new RangeSet and adds the provided Range
+     * @param r The range to add.
+     */
+    public RangeSet(Range r) {
+       this();
+       add(r);
+    }
+    
+    /**
+     * @param rs The set with which to union with this set.
+     * @return A new set that represents the union of this and the passed set.
+     */
+    public RangeSet union(RangeSet rs) {
+        // This should be rewritten to interleave the additions so that there 
+        // is fewer midlist insertions.
+        RangeSet result = new RangeSet();
+        result.add(this);
+        result.add(rs);
+        return result;
+    }
+    
+    /**
+     * @param rs The set with which to intersect with this set.
+     * @return new set that represents the intersct of this and the passed set.
+     */
+    public RangeSet intersect(RangeSet rs) {
+       RangeSet result = complement();
+       result.add(rs.complement());
+       return result.complement();
+    }
+    
+    /**
+     * @return The complement of this set, make sure to check for positive
+     * and negative infinity on the returned set.
+     */
+    public RangeSet complement() {
+       RangeSet rs = new RangeSet();
+        if (isEmpty()) {
+            rs.add(new Range(true,true));
+        } else {
+            if (!negInf) {
+                rs.add(new Range(true,ranges[0]-1));
+            }
+            for (int i=0;i<rangeCount-1;i++) {
+                rs.add(ranges[i*2+1]+1,ranges[i*2+2]-1);
+            }
+            if (!posInf) {
+                rs.add(new Range(ranges[(rangeCount-1)*2+1]+1,true));
+            }
+        }
+       return rs;
+    }
+       
+    /**
+     * @param i The integer to check to see if it is in this set..
+     * @return true if i is in the set.
+     */
+    public boolean contains(long i) {
+       int pos = binarySearch(i);
+       if (pos > 0) {
+           return true;
+       }
+       pos = -(pos+1);
+       if (pos % 2 == 0) {
+           return false;
+       } else {
+           return true;
+       }
+    }
+ 
+    /**
+     * //FIX unit test
+     * Checks to see if this set contains all of the elements of the Range.
+     *
+     * @param r The Range to see if this RangeSet contains it.
+     * @return true If every element of the Range is within this set.
+     */
+    public boolean contains(Range r) {
+       RangeSet rs = new RangeSet();
+       rs.add(r);
+       return intersect(rs).equals(rs);
+    }
+
+    /**
+     * Add all of the passed set's elements to this set.
+     *
+     * @param rs The set whose elements should be added to this set.
+     */
+    public void add(RangeSet rs) {
+        for (Iterator it=rs.iterator();it.hasNext();) {
+            add((Range) it.next());
+        }
+    }
+    
+
+    /**
+     * Add this range to the set.
+     *
+     * @param r The range to add
+     */
+    public void add(Range r) {
+        if (r.isMinNegInf()) {
+            negInf = true;
+        }
+        if (r.isMaxPosInf()) {
+            posInf = true;
+        }
+        add(r.getMin(),r.getMax());
+    }
+
+    /**
+     * Add a single integer to this set.
+     *
+     * @param i The int to add.
+     */
+    public void add(long i) {
+        add(i,i);
+    }
+    
+    /**
+     * Add a range to the set.
+     * @param min The min of the range (inclusive)
+     * @param max The max of the range (inclusive)
+     */
+    public void add(long min, long max) {
+
+       if (min > max) {
+           throw new IllegalArgumentException
+             ("min cannot be greater than max");
+       }
+
+       if (rangeCount == 0) { // first value.
+           insert(min,max,0);
+           return;
+       }
+       
+       // This case should be the most common (insert at the end) so we will
+       // specifically check for it.  Its +1 so that we make sure its not
+       // adjacent.  Do the MIN_VALUE check to make sure we don't overflow
+        // the long.
+       if (min != Long.MIN_VALUE && min-1 > ranges[(rangeCount-1)*2+1]) {
+           insert(min,max,rangeCount);
+           return;
+       } 
+
+       // minPos and maxPos represent inclusive brackets around the various
+       // ranges that this new range encompasses.  Anything within these
+       // brackets should be folded into the new range.
+       int minPos = getMinPos(min);
+       int maxPos = getMaxPos(max);
+       
+       // minPos and maxPos will switch order if we are either completely
+       // within another range or completely outside of any ranges.
+       if (minPos > maxPos) { 
+           if (minPos % 2 == 0) {
+               // outside of any ranges, insert.
+               insert(min,max,minPos/2);
+           } else {
+               // completely inside a range, nop
+           }
+       } else {
+           combine(min,max,minPos/2,maxPos/2);
+       }
+
+    }
+    
+    public void remove(RangeSet r) {
+        for (Iterator it=r.iterator();it.hasNext();) {
+            remove((Range) it.next());
+        }
+    }
+
+    public void remove(Range r) {
+        //FIX absolutely horrible implementation.
+        RangeSet rs = new RangeSet();
+        rs.add(r);
+        rs = intersect(rs.complement());
+        ranges = rs.ranges;
+        rangeCount = rs.rangeCount;
+        posInf = rs.posInf;
+        negInf = rs.negInf;
+    }
+
+    public void remove(long i) {
+        remove(new Range(i,i));
+    }
+
+    public void remove(long min, long max) {
+        remove(new Range(min,max));
+    }
+
+    /**
+     * @return An iterator of Range objects that this RangeSet contains.
+     */
+    public Iterator iterator() {
+       ArrayList l = new ArrayList(rangeCount);
+       for (int i=0;i<rangeCount;i++) {
+            if (rangeCount == 1 && negInf && posInf) {
+                l.add(new Range(true,true));
+            } else if (i == 0 && negInf) {
+                l.add(new Range(true,ranges[i*2+1]));
+            } else if (i == rangeCount-1 && posInf) {
+                l.add(new Range(ranges[i*2],true));
+            } else {
+                l.add(new Range(ranges[i*2],ranges[i*2+1]));
+            }
+       }
+       return l.iterator();
+    }
+
+    /**
+     * @return The number of integers in this set, -1 if infinate.
+     */
+    public long size() {
+        if (negInf || posInf) {
+            return -1;
+        }
+        long result = 0;
+        for (Iterator it=iterator();it.hasNext();) {
+            result+=((Range) it.next()).size();
+        }
+        return result;
+    }
+
+    /**
+     * @return true If the set doesn't contain any integers or ranges.
+     */
+    public boolean isEmpty() {
+        return rangeCount == 0;
+    }
+    
+    /**
+     * Parse a set of ranges seperated by commas.
+     *
+     * @see Range
+     *
+     * @param s The String to parse
+     * @return The resulting range
+     * @throws ParseException
+     */
+    public static RangeSet parse(String s) throws ParseException {
+       RangeSet rs = new RangeSet();
+       for (StringTokenizer st = new StringTokenizer(s,",");
+            st.hasMoreTokens();) {
+           rs.add(Range.parse(st.nextToken()));
+       }
+       return rs;
+    }
+
+    public int hashCode() {
+        int result = 0;
+        for (int i = 0; i < rangeCount*2; i++) {
+            result = (int) (91*result + ranges[i]);
+        }
+       return result;
+    }
+
+    public boolean equals(Object obj) {
+        if (obj instanceof RangeSet) {
+            RangeSet rs = (RangeSet) obj;
+            if (negInf == rs.negInf &&
+                posInf == rs.posInf &&
+                rangeCount == rs.rangeCount &&
+                Util.arraysEqual(ranges,0,rs.ranges,0,rangeCount*2)) {
+                return true;
+            }
+        }
+        return false;
+    }
+            
+    /**
+     * Outputs the Range in a manner that can be used to directly create
+     * a new range with the "parse" method.
+     */
+    public String toString() {
+       StringBuffer sb = new StringBuffer();
+        for (Iterator it=iterator();it.hasNext();) {
+           sb.append(it.next().toString());
+           if (it.hasNext()) {
+               sb.append(",");
+           }
+       }
+       return sb.toString();
+    }
+
+    public Object clone() {
+       RangeSet rs = new RangeSet();
+       rs.ranges = new long[ranges.length];
+       System.arraycopy(ranges,0,rs.ranges,0,ranges.length);
+       rs.rangeCount = rangeCount;
+       rs.posInf = posInf;
+       rs.negInf = negInf;
+       return rs;
+    }
+
+    private void combine(long min, long max, int minRange, int maxRange) {
+       // Fill in the new values into the "leftmost" range.
+       ranges[minRange*2] = Math.min(min,ranges[minRange*2]);
+       ranges[minRange*2+1] = Math.max(max,ranges[maxRange*2+1]);
+       
+       // shrink if necessary.
+       if (minRange != maxRange && maxRange != rangeCount-1) {
+           System.arraycopy(ranges,(maxRange+1)*2,ranges,(minRange+1)*2,
+                            (rangeCount-1-maxRange)*2);
+       }
+       
+       rangeCount -= maxRange-minRange;
+    }
+    
+    
+    /**
+     * @return the position of the min element within the ranges.
+     */
+    private int getMinPos(long min) {
+       // Search for min-1 so that adjacent ranges are included.
+       int pos = binarySearch(min == Long.MIN_VALUE ? min : min-1);
+       return pos >= 0 ? pos : -(pos+1);
+    }
+    
+    /**
+     * @return the position of the max element within the ranges.
+     */
+    private int getMaxPos(long max) {
+       // Search for max+1 so that adjacent ranges are included.
+       int pos = binarySearch(max == Long.MAX_VALUE ? max : max+1);
+       // Return pos-1 if there isn't a direct hit because the max
+       // pos is inclusive.
+       return pos >= 0 ? pos : (-(pos+1))-1;
+    }
+    
+    /**
+     * @see java.util.Arrays#binarySearch
+     */
+    private int binarySearch(long key) {
+       int low = 0;
+       int high = (rangeCount*2)-1;
+       
+       while (low <= high) {
+           int mid =(low + high)/2;
+           long midVal = ranges[mid];
+           
+           if (midVal < key) {
+                low = mid + 1;
+           } else if (midVal > key) {
+                high = mid - 1;
+           } else {
+                return mid; // key found
+           }
+       }
+       return -(low + 1);  // key not found.
+    }
+    
+    private void insert(long min, long max, int rangeNum) {
+       
+       // grow the array if necessary.
+       if (ranges.length == rangeCount*2) {
+           long[] newRanges = new long[ranges.length*2];
+           System.arraycopy(ranges,0,newRanges,0,ranges.length);
+           ranges = newRanges;
+       }
+       
+       if (rangeNum != rangeCount) {
+           System.arraycopy(ranges,rangeNum*2,ranges,(rangeNum+1)*2,
+                            (rangeCount-rangeNum)*2);
+       }
+       ranges[rangeNum*2] = min;
+       ranges[rangeNum*2+1] = max;
+       rangeCount++;
+    }
+
+    public static final void main(String[] args) throws Exception {
+       RangeSet rs = RangeSet.parse("5-10,15-20,25-30");
+       BufferedReader br = new BufferedReader(new InputStreamReader
+                                              (System.in));
+       while (true) {
+           System.out.println(rs.toString());
+           String s = br.readLine();
+           if (s.charAt(0) == '~') {
+               rs = rs.complement();
+           } else if (s.charAt(0) == 'i') {
+               rs = rs.intersect(RangeSet.parse(br.readLine()));
+           } else {
+               rs.add(RangeSet.parse(s));
+           }
+       }
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/RateCalculator.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/RateCalculator.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/RateCalculator.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,208 @@
+// (c) Copyright 2000 Justin F. Chapweske
+// (c) Copyright 2000 Ry4an C. Brase
+
+package com.onionnetworks.util;
+
+/**
+ * This class allows you to easily compute rate for various events.  A weighted
+ * floating average is used and all parameters can be tweaked to fine tune 
+ * your rate calculations.
+ * 
+ * @author Justin F. Chapweske
+ */ 
+public class RateCalculator {
+
+    public static final int DEFAULT_INTERVAL_LENGTH = 300; 
+    public static final int DEFAULT_HISTORY_SIZE = 100;     
+    public static final float DEFAULT_HISTORY_WEIGHT = .8f; 
+    
+    protected int intervalLength;  // Number of milliseconds in interval.
+    protected int historySize;      // Number of intervals to keep track of.
+    protected float historyWeight;  // Multipler/weight of old intervals.
+    protected double[] history;      // old intervals.
+    protected int historyPos;       // history is circular, this is index.
+    protected long lastIntervalTime = -1;  // last interval cutoff.
+    protected double currentIntervalEvents;  // Num events this interval.
+    protected double totalEvents;
+    protected double lastEstimatedEventCount;  // monotincally increasing.
+    protected long lastPositiveUpdateTime = -1;
+    protected long pauseTime = -1; // for pausing
+    
+    /**
+     * Construct a new RateCalculator using the default values.
+     */
+    public RateCalculator() {
+       this(DEFAULT_INTERVAL_LENGTH,DEFAULT_HISTORY_SIZE,
+             DEFAULT_HISTORY_WEIGHT);
+    }
+
+    /** 
+     * Construct a new RateCalculator.
+     * @param intervalLength The number of millis in each interval.
+     * @param historySize The number of intervals to keep track of.
+     * @param historyWeight The multipler used to determine the relevence of 
+     * older intervals.
+     *
+     * Using a small historySize and/or a low historyWeight will cause the rate
+     * to fit tightly against the current rate.  Using higher values allows
+     * the rate to be much smoother.  If this is being used for direct UI it
+     * is usually nice to have a frequently updated smooth rate.
+     */
+    public RateCalculator(int intervalLength, int historySize, 
+                          float historyWeight) {
+
+       this.intervalLength = intervalLength;
+       this.historySize = historySize;
+       this.historyWeight = historyWeight;
+
+        history = new double[historySize];
+        for (int i=0; i<history.length;i++) {
+            history[i]=-1;
+        }
+    }
+
+    public void pause() {
+       pause(System.currentTimeMillis());
+    }
+
+    /**
+     * Pauses the RateCalculator.  The RateCalculator will be resumed with the
+     * next call to resume(). It is not advised to update events during the
+     * paused period.
+     */
+    public void pause(long time) {
+       if (pauseTime != -1) {
+           throw new IllegalStateException("RateCalculator already paused");
+       }
+       pauseTime = time;
+    }
+
+    /**
+     * @return true if the RateCalculator is paused
+     */
+    public boolean isPaused() {
+       return pauseTime != -1;
+    }
+
+    /**
+     * Resumes the paused RateCalculator.
+     */
+    public void resume() {
+       resume(System.currentTimeMillis());
+    }
+
+    public void resume(long time) {
+       if (pauseTime == -1) {
+           throw new IllegalStateException("RateCalculator not paused");
+       }
+       update(0,time);
+       pauseTime = -1;
+    }
+    
+    /**
+     * This is the preferred way to update the number of events for rate
+     * calculation, it will simply use the time of the call to keep track of
+     * when the events occured.
+     */
+    public void update(double numEvents) {
+        update(numEvents,System.currentTimeMillis());
+    }
+
+
+    /**
+     * If you wish to specify exactly the time at which the events occured you
+     * can use this method.  It is very important that subsequent calls to
+     * update have eventTime's monotonically increasing.
+     */
+    public void update(double numEvents, long eventTime) { 
+       currentIntervalEvents += numEvents;
+       totalEvents += numEvents;
+        
+       // paused
+       if (pauseTime != -1) {
+           if (lastIntervalTime != -1) {
+               lastIntervalTime += (eventTime-pauseTime);
+           }
+           if (lastPositiveUpdateTime != -1) {
+               lastPositiveUpdateTime += (eventTime-pauseTime);
+           }
+           pauseTime = eventTime;
+       }
+
+        // first interval.
+       if (lastIntervalTime == -1) {
+           lastIntervalTime = eventTime;
+           lastPositiveUpdateTime = eventTime;
+            return;
+        }
+       
+       if (numEvents > 0) {
+           lastPositiveUpdateTime = eventTime;
+       }
+
+        long deltaTime = eventTime-lastIntervalTime;
+
+        if (deltaTime >= intervalLength) {
+           history[historyPos] = currentIntervalEvents / (double) deltaTime;
+           historyPos = (historyPos+1) % history.length; // circular
+           lastIntervalTime = eventTime;
+           currentIntervalEvents = 0;
+       }
+    }
+
+    public double getRate() {
+        return getRate(System.currentTimeMillis());
+    }
+
+    /**
+     * If you are specifying a time with update(long time) then you must
+     * specify the time at which you wish to view the rate for, this time
+     * must be greater than the last time that was passed to the update
+     * call.  This method DOES NOT allow you to see what the rate was at
+     * during a previous interval.
+     */
+    public double getRate(long time) {
+       update(0,time);
+       double rate=0,total=0,weight=1;
+       for (int i=history.length-1;i>=0;i--) {
+           double intervalRate = history[(historyPos + i) % history.length];
+
+           if (intervalRate == -1) { 
+                continue; 
+            }      
+
+           rate += intervalRate*weight;
+           total += weight;
+           weight *= historyWeight;
+       }
+
+       if (total ==0 && rate == 0) {
+           return currentIntervalEvents / ((double)(time-lastIntervalTime+1));
+       }
+        
+       return rate/total;
+    }
+
+    public double getEstimatedEventCount(double maxEvents) {
+        return getEstimatedEventCount(maxEvents,System.currentTimeMillis());
+    }
+    
+    public double getEstimatedEventCount(double maxEvents, long time) {
+       double rate = getRate(time);
+       long deltaTime = time-lastPositiveUpdateTime;
+       double estimatedEventCount = totalEvents + (deltaTime * rate);
+
+       return estimatedEventCount;
+    }
+    
+    public long getEstimatedTimeRemaining(double maxEvents) {
+       return getEstimatedTimeRemaining(maxEvents,
+                                        System.currentTimeMillis());
+    }
+      
+    public long getEstimatedTimeRemaining(double maxEvents, long time) {
+       double rate = getRate(time);
+       double estimatedEventCount = getEstimatedEventCount(maxEvents,time);
+       return (long) ((maxEvents-estimatedEventCount)/rate);
+    }
+}

Added: 
trunk/contrib/fec/common/src/com/onionnetworks/util/ReflectiveEventDispatch.java
===================================================================
--- 
trunk/contrib/fec/common/src/com/onionnetworks/util/ReflectiveEventDispatch.java
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/src/com/onionnetworks/util/ReflectiveEventDispatch.java
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,161 @@
+package com.onionnetworks.util;
+
+import java.util.*;
+import java.lang.reflect.Method;
+
+/**
+ * @author Justin Chapweske
+ */
+public class ReflectiveEventDispatch implements Runnable {
+
+    public static final int DEFAULT_WARNING_TIME = 10;
+
+    private Thread t;
+    private HashMap methodCache = new HashMap();
+    private HashMap listeners = new HashMap();
+    private LinkedList eventQueue = new LinkedList();
+    private ExceptionHandler handler;
+
+    public ReflectiveEventDispatch() {
+        t = new Thread(this,"Reflective Dispatch#" + hashCode());
+       t.setDaemon(true);
+        t.start();
+    }
+
+    public void setPriority(int priority) {
+        t.setPriority(priority);
+    }
+
+    public void setExceptionHandler(ExceptionHandler h) {
+       handler = h;
+    }
+
+    public synchronized void addListener(Object source, EventListener el, 
+                                        String methodName) {
+        this.addListener(source,el,new String[]{methodName});
+    }
+
+    public synchronized void addListener(Object source, EventListener el, 
+                                         String[] methodNames) {
+       HashMap hm = (HashMap) listeners.get(source);
+       if (hm == null) {
+           hm = new HashMap();
+           listeners.put(source, hm);
+       }
+
+        for (int i=0;i<methodNames.length;i++) {
+            HashSet set = (HashSet) hm.get(methodNames[i]);
+            if (set == null) {
+                set = new HashSet();
+                hm.put(methodNames[i],set);
+            }
+            set.add(el);
+        }
+    }
+
+    public synchronized void removeListener(Object source, EventListener el, 
+                                            String methodName) {
+        this.removeListener(source,el,new String[]{methodName});
+    }
+
+    public synchronized void removeListener(Object source, EventListener el, 
+                                            String[] methodNames) {
+       HashMap hm = (HashMap) listeners.get(source);
+       if (hm == null) {
+           throw new IllegalArgumentException("Listener not registered.");
+       }
+        for (int i=0;i<methodNames.length;i++) {
+            HashSet set = (HashSet) hm.get(methodNames[i]);
+            if (set == null || !set.contains(el)) {
+                throw new IllegalArgumentException("Listener not registered.");
+            }
+
+            set.remove(el);
+        }
+    }
+
+    public synchronized void fire(EventObject ev, String methodName) {
+        eventQueue.add(new Tuple(ev, methodName));
+        this.notifyAll();
+    }
+
+    public synchronized void close() {
+       // Place this on the queue to signify that we are done.
+       eventQueue.add(this);
+        this.notifyAll();
+    }
+
+    public void run() {
+       boolean done = false;
+
+        while (!done) {
+            EventObject ev = null;
+            String methodName = null;
+            HashSet set = null;
+            synchronized (this) {
+                if (eventQueue.isEmpty()) {
+                    try {
+                        this.wait();
+                    } catch (InterruptedException e) {
+                        done = true;
+                    }
+                    continue;
+                }
+               Object obj = eventQueue.removeFirst();
+               if (obj == this) {
+                   // If this is on the queue, it is time to close.
+                   done = true;
+                   continue;
+               }
+               Tuple t = (Tuple) obj;
+                ev = (EventObject) t.getLeft();
+                methodName = (String) t.getRight();
+               HashMap hm = (HashMap) listeners.get(ev.getSource());
+               if (hm == null) {
+                   continue;
+               }
+               set = (HashSet) hm.get(methodName);
+                if (set == null) {
+                    continue;
+                }
+                // Make a copy incase its modified while we're doing the shit.
+                set = (HashSet) set.clone();
+            }
+
+            for (Iterator it=set.iterator();it.hasNext();) {
+                EventListener el = (EventListener) it.next();
+                // Get the method and invoke it, passing the event.
+                //long t1 = System.currentTimeMillis();
+                try {
+                   final Class elc = el.getClass();
+                   final Class evc = ev.getClass();
+                  
+                   // Cache the method because getPublicMethod is very
+                   // expensive to invoke.
+                   Tuple cacheKey = new Tuple(elc,new Tuple(methodName,evc));
+                   Method m = (Method) methodCache.get(cacheKey);
+                   if (m == null) {
+                       final Class ca[] = new Class[] { evc };
+                       // This version of getMethod supports subclasses as
+                       // parameter types.
+                       m = Util.getPublicMethod(elc,methodName,ca);
+                       methodCache.put(cacheKey,m);
+                   }
+                   final Object oa[] = new Object[] { ev };
+                   m.invoke(el,oa);
+                } catch (Throwable t) {
+                   if (handler != null) {
+                       handler.handleException(new ExceptionEvent(this,t));
+                   } else {
+                       t.printStackTrace();
+                   }
+                }
+                //long t2 = System.currentTimeMillis()-t1;
+                //if (t2 > DEFAULT_WARNING_TIME) {
+                //    System.out.println(el+"."+methodName+"("+ev+") took"+
+                //                       " too long: "+t2+" millis");
+                //}
+            }
+        }
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/SimUtil.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/SimUtil.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/SimUtil.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,59 @@
+package com.onionnetworks.util;
+
+public class SimUtil {
+
+    /**
+     * Prints a command to be redirected to a file, and run as a shell script
+     * for invoking the GNU plotutils "graph" command.
+     */
+    public static final void printGraphCommand(String title, String x,
+                                               String y, int[][] plots, 
+                                               String[] graphNames) {
+        System.out.println("#!/bin/sh");
+        System.out.println("# By default (no args) it will display it in X, "+
+                           "use $1=gif,png,for images");
+        System.out.println("if [ -n \"$1\" ]");
+        System.out.println("then type=$1");
+        System.out.println("else type=\"X\"");
+        System.out.println("fi");
+
+        System.out.print("echo \"");
+        for (int i=0;i<plots.length;i++) {
+            for (int j=0;j<plots[i].length;j++) {
+                System.out.print(plots[i][j] + " ");
+            }
+            System.out.println("\n"); //blank line
+        }
+        System.out.print("\" | graph -W .003 -C -T $type -L \""+title+
+                         "\" -X \""+x+"\" -Y \""+y+"\"");
+    }
+
+
+    /**
+     * Yes, this could be one line, but this really needs to be readable
+     * as an error in this method could really screw things up.
+     */
+    public static final int getMedian(int[] data) {
+        // do a stupid bubble sort then pick middle.
+        // FIX, this sort doesn't have to suck.
+        for (int i=0;i<data.length-1;i++) {
+            for (int j=i+1;j<data.length;j++) {
+                if (data[i] > data[j]) {
+                    swap(data,i,j);
+                }
+            }
+        }
+        for (int i=0;i<data.length;i++) {
+            System.err.print(data[i]+" ");
+        }
+        System.err.println();
+        return data[data.length / 2];
+    }
+
+    public static final void swap(int[] data, int posA, int posB) {
+        int tmp = data[posA];
+        data[posA] = data[posB];
+        data[posB] = tmp;
+    }
+}
+    

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/TimedSoftHashMap.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/TimedSoftHashMap.java   
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/TimedSoftHashMap.java   
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,171 @@
+package com.onionnetworks.util;
+
+import java.util.*;
+import java.lang.ref.*;
+
+public class TimedSoftHashMap extends HashMap {
+
+    public static final int DEFAULT_TTL = 2*60*1000;
+
+    TreeSet timings = new TreeSet();
+
+    public TimedSoftHashMap() {
+        super();
+    }
+
+    public TimedSoftHashMap(Map t) {
+        throw new UnsupportedOperationException("this(Map t)");
+    }
+
+    public TimedSoftHashMap(int initialCapacity) {
+        super(initialCapacity);
+    }
+
+    public TimedSoftHashMap(int initialCapacity, float loadFactor) {
+        super(initialCapacity, loadFactor);
+    }
+
+    public boolean containsValue(Object value) {
+        return super.containsValue(new HashableSoftReference(value,0));
+    }
+
+    public Set entrySet() {
+        throw new UnsupportedOperationException("entrySet()");
+    }
+
+    /**
+     * We renew the timer every time get() is called.
+     */
+    public Object get(Object key) {
+        HashableSoftReference ref = (HashableSoftReference) super.get(key);
+        if (ref != null) {
+            ref.renew();
+        }
+        checkTimings();
+        return ref == null ? null : ref.get();
+    }
+
+    public boolean isEmpty() {
+        // In order to implement this method you need to clear out the
+        // garbage collected entries.  Shouldn't be hard but I don't have time
+        throw new UnsupportedOperationException("isEmpty()");
+    }
+
+    public Set keySet() {
+        throw new UnsupportedOperationException("entrySet()");
+    }
+
+    public Object put(Object key, Object value) {
+        return this.put(key,value,DEFAULT_TTL);
+    }
+
+    public Object put(Object key, Object value, int ttl) {
+        checkTimings();
+        HashableSoftReference hsr = new HashableSoftReference(value,ttl);
+        HashableSoftReference hsr2 = (HashableSoftReference)super.put(key,hsr);
+        timings.add(hsr);
+        if (hsr2 == null) {
+            return null;
+        } else {
+            timings.remove(hsr2);
+            return hsr2.get();
+        }
+    }
+
+    public void putAll(Map t) {
+        throw new UnsupportedOperationException("putAll(Map t)");
+    }
+
+    public Object remove(Object key) {
+        checkTimings();
+        Reference ref = (Reference) super.remove(key);
+        timings.remove(ref);
+        return ref == null ? null : ref.get();
+    }
+
+    public int size() {
+        // In order to implement this method you need to clear out the
+        // garbage collected entries.  Shouldn't be hard but I don't have time
+        throw new UnsupportedOperationException("size()");
+    }
+
+    public Collection values() {
+        throw new UnsupportedOperationException("values()");
+    }
+
+    public Object clone() {
+        throw new UnsupportedOperationException("clone()");
+    }
+
+    protected void checkTimings() {
+        long time = System.currentTimeMillis();
+        for (Iterator it=timings.iterator();it.hasNext();) {
+            HashableSoftReference hsr = (HashableSoftReference) it.next();
+            if (hsr.deathTime < time) {
+                for (Iterator it2=super.keySet().iterator();it2.hasNext();) {
+                    Object key = it2.next();
+                    if (super.get(key) == hsr) {
+                        it2.remove();
+                        it.remove();
+                        break;
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+    }
+
+
+    /**
+     * This class is only necessary for the containsValue calls, as the
+     * keys in the TimedSoftHashMap should not be SoftReferences and there
+     * for should already have equals() and hashCode() that works.
+     */
+    public class HashableSoftReference extends SoftReference implements
+        Comparable {
+
+        public long deathTime;
+        public int ttl;
+
+        public HashableSoftReference(Object ref, int ttl) {
+            super(ref);
+            this.ttl = ttl;
+            renew();
+        }
+
+        public void renew() {
+            this.deathTime = System.currentTimeMillis()+ttl;
+        }
+
+        public int compareTo(Object obj) {
+            HashableSoftReference hsr = (HashableSoftReference) obj;
+            if (hsr.deathTime == deathTime) {
+                return 0;
+            }
+            return hsr.deathTime < deathTime ? 1 : -1;
+        }
+
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                throw new NullPointerException();
+            }
+
+            Object thisObj = this.get();
+            if (thisObj == null) {
+                return false;
+            } else {
+                return thisObj.equals(obj);
+            }
+        }
+
+        public int hashCode() {
+            Object thisObj = this.get();
+            if (thisObj == null) {
+                return 0;
+            } else {
+                return thisObj.hashCode();
+            }
+        }
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/Tuple.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/Tuple.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/Tuple.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,47 @@
+package com.onionnetworks.util;
+
+/**
+ *
+ * @author Justin F. Chapweske
+ */
+public class Tuple {
+
+    protected Object left;
+    protected Object right;
+
+    public Tuple(Object left, Object right) {
+        this.left = left;
+        this.right = right;
+    }
+
+    public Object getCar() {
+        return left;
+    }
+
+    public Object getCdr() {
+        return right;
+    }
+
+    public Object getLeft() {
+        return left;
+    }
+
+    public Object getRight() {
+        return right;
+    }
+
+    public int hashCode() {
+        return left.hashCode() ^ right.hashCode();
+    }
+
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Tuple)) {
+            return false;
+        }
+        Tuple t = (Tuple) obj;
+        if (left.equals(t.getLeft()) && right.equals(t.getRight())) {
+            return true;
+        }
+        return false;
+    }
+}

Added: trunk/contrib/fec/common/src/com/onionnetworks/util/Util.java
===================================================================
--- trunk/contrib/fec/common/src/com/onionnetworks/util/Util.java       
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/src/com/onionnetworks/util/Util.java       
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,440 @@
+// (c) Copyright 2000 Justin F. Chapweske
+// (c) Copyright 2000 Ry4an C. Brase
+
+package com.onionnetworks.util;
+
+import java.util.*;
+import java.lang.reflect.*;
+
+public class Util {
+
+    private static final int MAX_ZERO_COPY = 16384;
+    private static byte[] zeroBytes;
+    private static char[] zeroChars;
+    private static char[] hexDigit = new char[] 
+        {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+    
+    // Must be global for shuffle
+    public static final Random rand = new Random(); 
+
+    public static final byte[] getBytes(int i) {
+        byte[] b = new byte[4];
+        b[0] = (byte) ((i >>> 24) & 0xFF);
+        b[1] = (byte) ((i >>> 16) & 0xFF);
+        b[2] = (byte) ((i >>> 8) & 0xFF);
+        b[3] = (byte) ((i >>> 0) & 0xFF);
+        return b;
+    }
+
+    public static final int getInt(byte[] b) {
+        return (((b[0]&0xFF) << 24) | ((b[1]&0xFF) << 16)
+           | ((b[2]&0xFF) << 8) | (b[3]&0xFF));
+    }
+
+    /**
+     * Fills in a range of an array with 0's.
+     *
+     * This method is meant to be a clone of the functionality of the C
+     * bzero function.  
+     *
+     * @param b The byte array to be 0'd
+     * @param off The offset within b to begin 0'ing
+     * @param len The number of bytes to be 0'd
+     */
+    public static final void bzero(byte[] b, int off, int len) {
+        if (zeroBytes == null) {
+            zeroBytes = new byte[64];
+        }
+        if (len < zeroBytes.length) {
+            System.arraycopy(zeroBytes,0,b,off,len);
+            return;
+        } else {
+            System.arraycopy(zeroBytes,0,b,off,zeroBytes.length);
+        }            
+
+        int zeroLength = zeroBytes.length;
+        do {
+            int delta = len-zeroLength;
+            int copyLength = zeroLength > delta ? delta : zeroLength;
+            if (copyLength > MAX_ZERO_COPY) {
+                copyLength = MAX_ZERO_COPY;
+            }
+            // We copy from close to the current position so we aren't
+            // thrashing mem for really large buffers.
+            System.arraycopy(b,off+zeroLength-copyLength,b,off+zeroLength,
+                             copyLength);
+            zeroLength+=copyLength;
+        } while(zeroLength < len);
+    }
+
+    /**
+     * Fills in a range of an array with 0's.
+     *
+     * This method is meant to be a clone of the functionality of the C
+     * bzero function.  
+     *
+     * @param b The char array to be 0'd
+     * @param off The offset within b to begin 0'ing
+     * @param len The number of chars to be 0'd
+     */
+    public static final void bzero(char[] b, int off, int len) {
+        if (zeroChars == null) {
+            zeroChars = new char[64];
+        }
+        if (len < zeroChars.length) {
+            System.arraycopy(zeroChars,0,b,off,len);
+            return;
+        } else {
+            System.arraycopy(zeroChars,0,b,off,zeroChars.length);
+        }            
+
+        int zeroLength = zeroChars.length;
+        do {
+            int delta = len-zeroLength;
+            int copyLength = zeroLength > delta ? delta : zeroLength;
+            if (copyLength > MAX_ZERO_COPY) {
+                copyLength = MAX_ZERO_COPY;
+            }
+            // We copy from close to the current position so we aren't
+            // thrashing mem for really large buffers.
+            System.arraycopy(b,off+zeroLength-copyLength,b,off+zeroLength,
+                             copyLength);
+            zeroLength+=copyLength;
+        } while(zeroLength < len);
+    }
+
+    public static final String getSpaces(int num) {
+       StringBuffer sb = new StringBuffer();
+       for (int i=0;i<num;i++) {
+           sb.append(" ");
+       }
+       return sb.toString();
+    }
+
+    public static boolean arraysEqual(int[] arr1, int start1, 
+                                      int[] arr2, int start2, int len) {
+       if (arr1 == arr2 && start1 == start2) {
+           return true;
+       }
+        for (int i=len-1;i>=0;i--) {
+            if (arr1[start1+i] != arr2[start2+i]) {
+                return false;
+            }
+        }
+        return true;
+    }    
+
+    public static boolean arraysEqual(long[] arr1, int start1, 
+                                      long[] arr2, int start2, int len) {
+       if (arr1 == arr2 && start1 == start2) {
+           return true;
+       }
+        for (int i=len-1;i>=0;i--) {
+            if (arr1[start1+i] != arr2[start2+i]) {
+                return false;
+            }
+        }
+        return true;
+    }    
+
+    public static boolean arraysEqual(char[] arr1, int start1, 
+                                      char[] arr2, int start2, int len) {
+       if (arr1 == arr2 && start1 == start2) {
+           return true;
+       }
+        for (int i=len-1;i>=0;i--) {
+            if (arr1[start1+i] != arr2[start2+i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean arraysEqual(byte[] arr1, int start1, 
+                                      byte[] arr2, int start2, int len) {
+       if (arr1 == arr2 && start1 == start2) {
+           return true;
+       }
+        for (int i=len-1;i>=0;i--) {
+            if (arr1[start1+i] != arr2[start2+i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Fisher-Yates shuffle.
+     */
+    public static final void shuffle(int[] list) {
+        for (int i = list.length-1; i >= 0; i--) {
+            int j = rand.nextInt(i+1);
+            if (i == j) {
+                continue;
+            }
+            int tmp = list[i];
+            list[i] = list[j];
+            list[j] = tmp;
+        }
+    }
+
+    public static final void shuffle(boolean[] list) {
+        for (int i = list.length-1; i >= 0; i--) {
+            int j = rand.nextInt(i+1);
+            if (i == j) {
+                continue;
+            }
+            boolean tmp = list[i];
+            list[i] = list[j];
+            list[j] = tmp;
+        }
+    }
+
+    public static final void shuffle(Object[] list) {
+        for (int i = list.length-1; i >= 0; i--) {
+            int j = rand.nextInt(i+1);
+            if (i == j) {
+                continue;
+            }
+            Object tmp = list[i];
+            list[i] = list[j];
+            list[j] = tmp;
+        }
+    }
+
+
+    public static final byte[] getBytes(char[] chars) {
+       byte[] retval = new byte[chars.length*2];
+        arraycopy(chars,0,retval,0,retval.length);
+        return retval;
+    }
+
+    public static final char[] getChars(byte[] bytes) {
+        int len = bytes.length;
+       if (len % 2 != 0) {
+           throw new IllegalArgumentException("Input array.length non-even.");
+       }
+        char[] retval = new char[len/2];
+        arraycopy(bytes,0,retval,0,len);
+        return retval;
+    }
+
+    public static final void arraycopy(char[] chars, int charOff, 
+                                       byte[] bytes, int byteOff, 
+                                       int numBytes) {
+       int indexCounter = byteOff;
+        int loopMax = numBytes/2+charOff;
+       for (int i=charOff; i<loopMax; i++) {
+           bytes[indexCounter++] = (byte)((chars[i] & 0xFF00) >> 8);
+           bytes[indexCounter++] = (byte)(chars[i] & 0xFF);
+       }
+        // copy the straggler, if any.
+        if (numBytes % 2 != 0) {
+            bytes[indexCounter] = (byte)((chars[loopMax] & 0xFF00) >> 8);
+        }
+    }
+
+    /** Dumps a byte array to an UNIX style hex dump.  This isn't terribly
+     * efficient so you should probably try to limit it to debug code.
+     * @param byte[] the byte to dump
+     */
+    public static String getHexDump(byte[] b) {
+       int pos = 0; 
+       final int INDEX_WIDTH = 7;
+       final String ZEROS = "0000000"; // must be at least INDEX_WIDTH length
+       StringBuffer sb = new StringBuffer();
+       while (pos < b.length) {
+           if ((pos % 16) == 0) {
+               if (pos > 0) {
+                   sb.append("\n");
+               }
+               String index = Integer.toOctalString(pos);
+               sb.append(ZEROS.substring(0,INDEX_WIDTH-index.length()));
+               sb.append(index).append(" ");
+           } else if ((pos % 4) == 0) {
+               sb.append(" ");
+           }
+           String val = Integer.toHexString(b[pos] & 0xFF);
+           if (val.length() == 1) {
+               sb.append("0");
+           }
+           sb.append(val);
+           pos++;
+       }
+       return sb.toString();
+    }
+
+    public static final void arraycopy(byte[] bytes, int byteOff, 
+                                       char[] chars, int charOff, 
+                                       int numBytes) {
+       int indexCounter = byteOff;
+        int loopMax = numBytes/2+charOff;
+       for (int i=charOff; i<loopMax; i++) {
+           chars[i] = (char)(((bytes[indexCounter++]&0xFF)<<8) 
+                              | (bytes[indexCounter++]&0xFF));
+       }
+        // copy the straggler, if any.
+        if (numBytes % 2 != 0) {
+           chars[loopMax] = (char)((bytes[indexCounter]&0xFF)<<8);
+        }
+    }
+
+    /**
+     * Divides and rounds up on remainder
+     */
+    public static int divideCeil(int num, int denom) {
+        return num/denom + ((num%denom==0)?0:1);
+    }
+    
+    public static int divideCeil(long num, long denom) {
+        return (int) (num/denom + ((num%denom==0)?0:1));
+    }
+
+    /**
+     * @param a The number take take the log base 2 of.
+     * @return the log base 2 of the supplied number.
+     */
+    public static double log2(double a) {
+       return Math.log(a)/Math.log(2);
+    }
+    
+    public static String bytesToHex(Buffer b) {
+        return bytesToHex(b.b,b.off,b.len);
+    }
+
+    public static String bytesToHex(byte[] in) {
+        return bytesToHex(in,0,in.length);
+    }
+
+    /** Turn a byte array into a lowercase hex string
+     */
+    public static String bytesToHex(byte[] in, int off, int len) {
+        char[] out = new char[in.length * 2];
+       for (int i = 0; i < len; i++) {
+            out[i*2] = hexDigit[(0xF0 & in[i+off]) >> 4]; // high nybble
+           out[i*2+1] = hexDigit[0xF  & in[i+off]]; // low nybble
+       }
+       return new String(out);
+    }
+
+    /** Create a byte array from a hex string
+     */
+    public static byte[] hexToBytes(String in) {
+        int len = in.length();
+       if (len % 2 != 0) {
+           throw new IllegalArgumentException("Even length string expected.");
+       }
+       byte[] out = new byte[len/2];
+       try {
+           for (int i = 0; i < out.length; i++) {
+               out[i] = (byte)(Integer.parseInt(in.substring(i*2, i*2+2), 16));
+           }
+       } catch (NumberFormatException doh) {
+           doh.printStackTrace();
+           throw new IllegalArgumentException("ParseError");
+       }
+       return out;
+    }
+
+    /** Check if an IP address is probably inside a NAT.  Data culled from
+     * RFC 790.
+     * @param byte[] the 4 byte long IP address to check
+     * @return true if the address is probably inside a NAT
+     */
+    public static boolean isProbablyNat(byte[] addr) {
+        if (addr.length != 4) {
+            throw new IllegalArgumentException("Address must be 4 bytes long");
+        }
+       int a = 0xFF & addr[0];
+       int b = 0xFF & addr[1];
+       int c = 0xFF & addr[2];
+       int d = 0xFF & addr[3];
+       return ((a == 10)
+               || (a==192 && b==168)
+               || (a==192 && b==0 && c==1)
+               || (a==223 && b==255 && c==255));
+    }
+
+    /**
+     * Class.getMethod requires exact parameters for the types.  This
+     * method is more fuzzy and just finds the first one that works.  This
+     * class will also prefer public classes/methods.
+     */
+    public static final Method getPublicMethod(Class clazz, String name, 
+                                               Class[] types) 
+        throws NoSuchMethodException {
+
+        Class c = clazz;
+        while (c != null) {
+            if (Modifier.isPublic(c.getModifiers())) {
+                Method m = getMethod(c.getMethods(),name,types);
+                if (m != null) {
+                    return m;
+                }
+            }
+            
+            // check the interfaces.
+            Class[] interfs = clazz.getInterfaces();
+            for (int a=0;a<interfs.length;a++) {
+                if (!Modifier.isPublic(interfs[a].getModifiers())) {
+                    continue;
+                }
+                Method m = getMethod(interfs[a].getMethods(),name,types);
+                if (m != null) {
+                    return m;
+                }
+            }
+            // climb up the superclass chain.
+            c = c.getSuperclass();
+        }
+        throw new NoSuchMethodException();
+    }  
+    
+
+    /**
+     * @return a public method that matches the signature, null if none match.
+     */
+    public static final Method getMethod(Method[] methods, String name,
+                                         Class[] types) {
+
+        for (int i=0;i<methods.length;i++) {
+            if (Modifier.isPublic(methods[i].getModifiers()) &&
+                name.equals(methods[i].getName()) && 
+                types.length == methods[i].getParameterTypes().length) {
+                
+               if (types.length == 0) {
+                   return methods[i];
+               }
+
+                for (int j=0;j<types.length;j++) {
+                    if (!methods[i].getParameterTypes()[j].
+                        isAssignableFrom(types[j])) {
+                        break;
+                    } else if (j == types.length-1) {
+                        return methods[i];
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Creates an IntIterator from an Iterator containing Integer objects.
+     */
+    public static final IntIterator createIntIterator(final Iterator it) {
+        return new IntIterator() {
+                public boolean hasNextInt() {
+                    return it.hasNext();
+                }
+
+                public int nextInt() {
+                    return ((Integer) it.next()).intValue();
+                }
+
+                public void removeInt() {
+                    it.remove();
+                }
+            };
+    }
+}

Added: trunk/contrib/fec/common/test/build.xml
===================================================================
--- trunk/contrib/fec/common/test/build.xml     2006-11-10 20:02:12 UTC (rev 
10866)
+++ trunk/contrib/fec/common/test/build.xml     2006-11-10 20:10:52 UTC (rev 
10867)
@@ -0,0 +1,91 @@
+<!--
+The default target is "test", which will build all tests and run them.
+
+Other targets:
+
+  clean         - Remove all generated files.
+  prepare       - Set up build directory structure.
+-->
+
+<project name="Tests" default="test" basedir="..">
+
+  <property environment="env"/>
+
+  <!-- ==================================================================== -->
+  <target name="prepare">
+    <mkdir dir="${test.classes}" />
+    <mkdir dir="${test.results}" />
+    <available property="junit.task.available" 
+               
classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask" />
+    <available property="junit.available" 
+               classname="junit.framework.Test" />
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="tidy">
+    <delete dir="${test.classes}" quiet="true"/>
+    <delete dir="${test.results}" quiet="true"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="clean" depends="tidy">
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="classes" depends="prepare">
+    <javac srcdir="${test.src}"
+           destdir="${test.classes}"
+          classpath="${test.classpath}"
+           debug="true"
+           optimize="false"
+           deprecation="${javac.deprecation}">
+       <include name="**/*.java"/>
+    </javac>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="test" depends="junit"/>
+
+  <!-- ==================================================================== -->
+  <target name="junit" depends="prepare,check.junit.task,check.junit,classes">
+    <echo>
+Test results in ${test.results}
+    </echo>
+    <delete file="${test.results}/*" quiet="true" />
+    <junit fork="no" printsummary="yes" haltonerror="yes" haltonfailure="yes" >
+      <formatter type="plain" usefile="true" />                  
+      <classpath> 
+         <pathelement path="${test.classpath}" />
+         <pathelement location="${junit.jar}" />     
+         <pathelement location="${test.classes}" />     
+      </classpath>        
+      <batchtest fork="no" todir="${test.results}">
+        <fileset dir="${test.src}">
+          <include name="**/*Test*.java" />
+        </fileset>
+      </batchtest>   
+    </junit>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="check.junit.task" unless="junit.task.available" >
+    <fail message="
+ The &lt;junit&gt; task is not available.
+ You need to install the Ant optional tasks .jar in the [ant]/lib directory."
+   />
+  </target>
+
+  <target name="check.junit" unless="junit.available" >
+    <fail message="
+  The junit .jar is not in the Ant class path.
+  You need to place a copy junit.jar in the [ant]/lib directoy." 
+    />
+  </target>
+</project>
+
+
+
+
+
+
+

Added: 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/BlockingRAFTest.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/io/BlockingRAFTest.java 
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/io/BlockingRAFTest.java 
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,195 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+import junit.framework.*;
+
+public class BlockingRAFTest extends TestCase {
+
+    byte[] b = new byte[8192*16];
+    Random rand = new Random();
+
+    public BlockingRAFTest(String name) {
+       super(name);
+       for (int i=0;i<b.length;i++) {
+           b[i] = (byte) i;
+       }
+    }
+
+    public void testZeroRead() {
+       byte[] b2 = new byte[8192];
+       try {
+           BlockingRAF raf = new BlockingRAF(new TempRaf());
+           raf.seekAndWrite(0,b,0,b.length);
+           raf.seekAndRead(0,b2,0,0);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+
+    public void testZeroWrite() {
+       byte[] b2 = new byte[8192];
+       try {
+           BlockingRAF raf = new BlockingRAF(new TempRaf());
+           raf.seekAndWrite(0,b,0,0);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+
+    public void testEOF() {
+       byte[] b2 = new byte[8192];
+       try {
+           BlockingRAF raf = new BlockingRAF(new TempRaf());
+           raf.setReadOnly();
+           assertEquals(raf.seekAndRead(0,b2,0,b2.length),-1);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+
+       try {
+           BlockingRAF raf = new BlockingRAF(new TempRaf());
+           raf.seekAndWrite(0,b,0,b.length);
+           raf.setReadOnly();
+           raf.seekAndRead(0,b2,0,b2.length);
+           assertEquals(raf.seekAndRead(b.length,b2,0,b2.length),-1);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+
+    public void testException() {
+       byte[] b2 = new byte[8192];
+       try {
+           BlockingRAF raf = new BlockingRAF(new TempRaf());
+           raf.setException(new IOException());
+           raf.seekAndRead(0,b2,0,b2.length);
+           fail("Should have thrown exception");
+       } catch (IOException e) {
+       }
+    }
+
+    public void testClose() {
+       byte[] b2 = new byte[8192];
+       try {
+           BlockingRAF raf = new BlockingRAF(new TempRaf());
+           raf.close();
+           raf.seekAndRead(0,b2,0,b2.length);
+           fail("Should have thrown exception");
+       } catch (IOException e) {
+       }
+    }
+
+    public void testMega() {
+       for (int i=0;i<50;i++) {
+           doTestMega();
+       }
+    }
+
+    public void doTestMega() {
+       try {
+           final BlockingRAF raf = new BlockingRAF(new TempRaf());
+           
+           int writerCount = 3;
+           int readerCount = 3;
+
+           Thread[] writers = new Thread[writerCount];
+
+           // Startup a number of random writer threads.
+           for (int i=0;i<writerCount;i++) {
+               writers [i] = new Thread() {
+                       public void run() {
+                           RangeSet rs = new RangeSet();
+                           try {
+                               int min,max;
+                               
+                               while (rs.size() != b.length) {
+                                   if (rs.isEmpty()) {
+                                       min = 0;
+                                   } else if (rand.nextInt(10) == 0) {
+                                       min = (int) ((Range) rs.iterator().
+                                                    next()).getMax()+1;
+                                   } else {
+                                       min = (int) ((Range) rs.iterator().
+                                                    next()).
+                                           getMax()+rand.nextInt
+                                           (b.length-(int)rs.size()); 
+                                   }
+                                   
+                                   max = rand.nextInt(b.length-min)+min;
+                                   rs.add(min,max);
+                                   //System.out.println("min="+min+
+                                   //         ",max="+max);
+                                   //System.out.println(rs);
+                                   raf.seekAndWrite(min,b,min,max-min+1);
+                               }
+                           } catch (IOException e) {
+                               e.printStackTrace(System.out);
+                               fail(""+e);
+                           } 
+                       }
+                   };
+               writers[i].start();
+           }
+           
+           final Thread[] readers = new Thread[readerCount];
+           
+           for (int i=0;i<readerCount;i++) {
+               readers[i] = new Thread() {
+                       public void run() {
+                           RAFInputStream ris = new RAFInputStream(raf);
+           
+                           ByteArrayOutputStream baos = 
+                               new ByteArrayOutputStream();
+
+                           // assign different size buffers.
+                           byte[] b2 = new byte[8192*
+                                               (rand.nextInt(5)+1)];
+                           int c;
+                           try {
+                               while ((c = ris.read(b2)) != -1) {
+                                   baos.write(b2,0,c);
+                               }
+                               baos.close();
+                               assert(Util.arraysEqual(baos.toByteArray(),
+                                                       0,b,0,b.length));
+                           } catch (IOException e) {
+                               fail(""+e);
+                           }
+                       }
+                   };
+               
+               // start the reader
+               readers[i].start();
+           }
+
+           // Wait for the writers to finish.
+           for (int i=0;i<writerCount;i++) {
+               try {
+                   writers[i].join();
+               } catch (InterruptedException e) {
+                   fail(""+e);
+               }
+           }
+           // set to read-only once they are done writing.
+           raf.setReadOnly();
+
+
+           // Wait for the readers to finish.
+           for (int i=0;i<readerCount;i++) {
+               try {
+                   readers[i].join();
+               } catch (InterruptedException e) {
+                   fail(""+e);
+               }
+           }
+           // Close the raf when all done.
+           raf.close();
+
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+}
+

Added: 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/FiniteInputStreamTest.java
===================================================================
--- 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/FiniteInputStreamTest.java
   2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/FiniteInputStreamTest.java
   2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,265 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+import java.text.ParseException;
+
+import junit.framework.*;
+
+/**
+ * These are poor quality test cases.  Please improve.
+ */
+public class FiniteInputStreamTest extends TestCase {
+
+    private static Random rand = new Random();
+
+    private static byte[] b = new byte[1000];
+    
+    public FiniteInputStreamTest(String name) {
+       super(name);
+
+       for (int i=0;i<b.length;i++) {
+              b[i] = (byte) i;
+        }
+       
+       // ByteArrayInputStream bais = new ByteArrayInputStream(b);
+    }
+
+    public void testNullInputStream() {         
+       try {
+           int r = rand.nextInt(b.length);
+           
+           FiniteInputStream fis = new FiniteInputStream(null, r);
+           
+           fail("should have thrown exception");               
+           
+       } catch(RuntimeException ex) {
+       }
+    }
+    
+    public void testZeroCountInput() {
+       
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       
+       
+       FiniteInputStream fis = new FiniteInputStream(bais, 0);
+
+       try {
+           int z = fis.read(b, 0, 0);
+           assertEquals(z,0);
+       } catch (IOException ex) {
+           fail("Threw exception while reading zero length: " + ex);
+       }
+
+       int r = rand.nextInt(b.length);
+       
+       try {
+           assertEquals(fis.read(b, 0, r),-1);
+       } catch (IOException e) {
+           fail("exception thrown");
+       }
+   }
+
+    public void testReadToEnd() {
+
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       byte[] blah = new byte[r+1];
+
+       
+       try {
+           FiniteInputStream fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r);
+       } catch (IOException ex) {
+           fail("Threw exception while reading exact length of contents: " + 
ex);
+       }
+
+    }
+
+    public void testAvailable() {
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+
+       byte[] b2 = new byte[b.length-1];
+       try {
+           FiniteInputStream fis = new FiniteInputStream(bais,b2.length);
+           fis.read(b2,0,b2.length);
+           assertEquals(fis.available(),0);
+       } catch (IOException ex) {
+           fail(""+ex);
+       }
+    }
+
+    public void testReadToEndThenReadZero() {
+
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       byte[] blah = new byte[r+1];
+
+       
+       try {
+           FiniteInputStream fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r);
+           int res = fis.read(blah, 0, 0);
+           assertEquals(res, 0);
+       } catch (IOException ex) {
+           fail("Threw exception while reading exact length of contents: " + 
ex);
+       }
+
+    }  
+
+    public void testReadPastEnd() {
+
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       byte[] blah = new byte[r+1+10]; // the plus ten is just so we dont get 
an IndexOutOfBounds
+                                       // exception when we read past the end, 
since the initial 
+                                       // size of this array is usually the 
same size as the 
+                                       // amount to be read in 
+
+       
+       FiniteInputStream fis = null;
+       try {
+           fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r);
+       } catch (IOException ex) {
+           fail("Threw exception while reading exact length of contents: " + 
ex);
+       }
+       
+       try {
+           fis.read();
+           assertEquals(fis.read(blah,0,1),-1);
+       } catch (IOException ex) {
+           fail("exception thrown");
+       }
+ 
+    }
+
+   public void testLengthReturnAccuracy() {
+       
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       byte[] blah = new byte[r+1];
+
+       FiniteInputStream fis = new FiniteInputStream(bais, r);
+       int r2 = rand.nextInt(r);
+       int len = -999;
+       try {
+           len = fis.read(blah, 0, r2);
+       } catch (IOException ex) {
+           fail("Threw exception while reading contents: " + ex);
+       }
+
+       assertEquals(len,r2);
+       
+
+   }
+
+   public void testSkipPastEnd() {
+
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       int r2 = rand.nextInt(r);
+       byte[] blah = new byte[r+1+r]; // the extra r is just so we dont get an 
IndexOutOfBounds
+                                       // exception when we read past the end, 
since the initial 
+                                       // size of this array is usually the 
same size as the 
+                                       // amount to be read in 
+
+       FiniteInputStream fis = null;
+       try {
+           fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r2);
+       } catch (IOException ex) {
+           fail("Threw exception while reading contents: " + ex);
+       }
+       
+       try {
+           fis.skip(r);
+       } catch(IOException ex) {
+           fail(""+ex);
+       }
+ 
+    }
+
+   public void testSkipToEnd() {
+
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       int r2 = rand.nextInt(r);
+       byte[] blah = new byte[r+1+r]; // the extra r is just so we dont get an 
IndexOutOfBounds
+                                       // exception when we read past the end, 
since the initial 
+                                       // size of this array is usually the 
same size as the 
+                                       // amount to be read in 
+
+       FiniteInputStream fis = null;   
+       try {
+           fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r2);
+       } catch (IOException ex) {
+           fail("Threw exception while reading contents: " + ex);
+       }
+       
+       try {
+           fis.skip(r-r2);
+       } catch(IOException ex) {
+           fail("should be able to skip to end ");     
+       }
+       try {
+           fis.read(blah,0,0);
+       } catch(IOException ex) {
+           fail("should be able to skip to end ");     
+       }
+       try {
+           fis.read();
+           fis.read(blah,0,1);
+       } catch (IOException ex) {
+           fail(""+ex);
+       }
+ 
+    }
+
+    public void testSkipNegative() {
+
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       int r2 = rand.nextInt(r);
+       byte[] blah = new byte[r+1]; 
+
+       FiniteInputStream fis = null;
+       try {
+           fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r2);
+       } catch (IOException ex) {
+           fail("Threw exception while reading contents: " + ex);
+       }
+       
+       try {
+           fis.skip(0-rand.nextInt(r));
+       } catch(IOException ex) {
+           fail(""+ex);
+       }
+ 
+    }
+
+
+
+   public void testContents() {
+       ByteArrayInputStream bais = new ByteArrayInputStream(b);
+       int r = rand.nextInt(b.length);
+       byte[] blah = new byte[r+1];
+
+       
+       try {
+           FiniteInputStream fis = new FiniteInputStream(bais, r);
+           fis.read(blah, 0, r);
+           
+           for (int i=0;i<r;i++) {
+              assertEquals(b[i],blah[i]);
+            }
+           
+       } catch (IOException ex) {
+           fail("Threw exception while reading exact length of contents: " + 
ex);
+       }
+   }
+}
+

Added: 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/WriteCommitRafTest.java
===================================================================
--- 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/WriteCommitRafTest.java  
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/test/src/com/onionnetworks/io/WriteCommitRafTest.java  
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,192 @@
+package com.onionnetworks.io;
+
+import com.onionnetworks.util.*;
+import java.io.*;
+import java.util.*;
+import junit.framework.*;
+
+public class WriteCommitRafTest extends TestCase {
+
+    byte[] b = new byte[8192*16];
+    Random rand = new Random();
+
+    public WriteCommitRafTest(String name) {
+       super(name);
+       for (int i=0;i<b.length;i++) {
+           b[i] = (byte) i;
+       }
+    }
+
+    public void testZeroRead() {
+       byte[] b2 = new byte[8192];
+       try {
+           WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           raf.seekAndWrite(0,b,0,b.length);
+           raf.seekAndRead(0,b2,0,0);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+
+    public void testZeroWrite() {
+       byte[] b2 = new byte[8192];
+       try {
+           WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           raf.seekAndWrite(0,b,0,0);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+
+    public void testEOF() {
+       byte[] b2 = new byte[8192];
+       try {
+           WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           raf.setReadOnly();
+           assertEquals(raf.seekAndRead(0,b2,0,b2.length),-1);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+
+       try {
+           WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           raf.seekAndWrite(0,b,0,b.length);
+           raf.setReadOnly();
+           raf.seekAndRead(0,b2,0,b2.length);
+           assertEquals(raf.seekAndRead(b.length,b2,0,b2.length),-1);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+
+    public void testException() {
+       byte[] b2 = new byte[8192];
+       try {
+           WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           raf.setException(new IOException());
+           raf.seekAndRead(0,b2,0,b2.length);
+           fail("Should have thrown exception");
+       } catch (IOException e) {
+       }
+    }
+
+    public void testClose() {
+       byte[] b2 = new byte[8192];
+       try {
+           WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           raf.close();
+           raf.seekAndRead(0,b2,0,b2.length);
+           fail("Should have thrown exception");
+       } catch (IOException e) {
+       }
+    }
+
+    public void testMega() {
+       for (int i=0;i<50;i++) {
+           doTestMega();
+       }
+    }
+
+    public void doTestMega() {
+       try {
+           final WriteCommitRaf raf = new WriteCommitRaf(new TempRaf());
+           // Start the writer thread.
+           (new Thread() {
+                   public void run() {
+                       RangeSet rs = new RangeSet();
+                       try {
+                           int min,max;
+                           
+                           while (rs.size() != b.length) {
+                               if (rs.isEmpty()) {
+                                   min = 0;
+                               } else if (rand.nextInt(10) == 0) {
+                                   min = (int) ((Range) rs.iterator().
+                                                next()).getMax()+1;
+                               } else {
+                                   min = (int) ((Range) rs.iterator().
+                                                next()).
+                                       getMax()+rand.nextInt
+                                       (b.length-(int)rs.size()); 
+                               }
+                               
+                               max = rand.nextInt(b.length-min)+min;
+                               // See where this range overlaps.
+                               RangeSet available = rs.complement().
+                                   intersect(new RangeSet
+                                       (new Range(min,max)));
+                               if (!available.isEmpty()) {
+                                   Range r = (Range) available.iterator().
+                                       next();
+                                   min = (int) r.getMin();
+                                   max = (int) r.getMax();
+                                   
+                                   rs.add(min,max);
+                                   //System.out.println("min="+min+
+                                   //         ",max="+max);
+                                   //System.out.println(rs);
+                                   raf.seekAndWrite(min,b,min,max-min+1);
+                               } else {
+                                   // Do a zero write
+                                   raf.seekAndWrite(min,b,min,0);
+                               }
+                           }
+                           // set to read-only once they are done writing.
+                           raf.setReadOnly();
+                       } catch (IOException e) {
+                           e.printStackTrace(System.out);
+                           fail(""+e);
+                       } 
+                   }
+               }).start();
+           
+           int readerCount = 5;
+           final Thread[] readers = new Thread[readerCount];
+           
+           for (int i=0;i<readerCount;i++) {
+               readers[i] = new Thread() {
+                       public void run() {
+                           InputStream ris = new RAFInputStream(raf);
+                           ris = new UnpredictableInputStream(ris);
+           
+                           ByteArrayOutputStream baos = 
+                               new ByteArrayOutputStream();
+
+                           // assign different size buffers.
+                           byte[] b2 = new byte[8192*
+                                               (rand.nextInt(5)+1)];
+                           int c;
+                           try {
+                               while ((c = ris.read(b2)) != -1) {
+                                   baos.write(b2,0,c);
+                               }
+                               baos.close();
+                               assert(Util.arraysEqual(baos.toByteArray(),
+                                                       0,b,0,b.length));
+                           } catch (IOException e) {
+                               fail(""+e);
+                           }
+                       }
+                   };
+               
+               // start the reader
+               readers[i].start();
+           }
+
+           // Wait for the readers to finish.
+           for (int i=0;i<readerCount;i++) {
+               try {
+                   readers[i].join();
+               } catch (InterruptedException e) {
+                   fail(""+e);
+               }
+           }
+           // Close the raf when all done.
+           raf.close();
+
+       } catch (IOException e) {
+           fail(""+e);
+       }
+    }
+}
+

Added: 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/AsyncPersistentPropsTest.java
===================================================================
--- 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/AsyncPersistentPropsTest.java
      2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/AsyncPersistentPropsTest.java
      2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,65 @@
+package com.onionnetworks.util;
+
+import java.util.*;
+import java.io.*;
+
+import junit.framework.*;
+
+public class AsyncPersistentPropsTest extends TestCase {
+
+    private Random rand = new Random();
+    
+    public AsyncPersistentPropsTest(String name) {
+       super(name);
+    }
+
+    public void testReadWrite() {
+        for (int a=0;a<100;a++) {
+            try {
+                File f = File.createTempFile("swarmtest","tmp");
+                f.deleteOnExit();
+                AsyncPersistentProps app = new AsyncPersistentProps(f);
+                Properties props = new Properties();
+                for (int i=0;i<100;i++) {
+                    String key = new Integer(rand.nextInt(1000)).toString();
+                    String value = new Integer(rand.nextInt(1000)).toString();
+                    app.setProperty(key,value);
+                    props.setProperty(key,value);
+                    if ((i % 10) == 0) { // decimate
+                        app.remove(key);
+                        props.remove(key);
+                    }
+                }
+                app.close();
+                app = new AsyncPersistentProps(f);
+                assertEquals(props,app.getProperties());
+
+                // cleanup
+                app.close();
+            } catch (IOException e) {
+                fail(e.getMessage());
+            }
+        }
+    }
+
+    public void testException() {
+       File f = new File("a/b/c/d/f/g/h.tmp");
+       if (f.getParentFile().exists()) {
+           fail(f+" shouldn't exist for this test");
+       }
+       AsyncPersistentProps app = null;
+       try {
+           app = new AsyncPersistentProps(f);
+       } catch (IOException e) {
+           fail(""+e);
+       }
+       
+       try {
+           app.setProperty("foo","bar");
+           app.flush();
+           app.close();
+           fail("Exception should have been thrown");
+       } catch (IOException e) {}
+    }
+}
+                

Added: 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/BlockDigestInputStreamTest.java
===================================================================
--- 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/BlockDigestInputStreamTest.java
    2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/BlockDigestInputStreamTest.java
    2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,58 @@
+package com.onionnetworks.util;
+
+import java.security.*;
+import java.util.*;
+import java.io.*;
+
+import junit.framework.*;
+
+public class BlockDigestInputStreamTest extends TestCase {
+
+    public static final String ALGORITHM = "SHA";
+
+    public static final Random rand = new Random();
+    
+    public BlockDigestInputStreamTest(String name) {
+       super(name);
+    }
+
+    public void testBlockCount() throws Exception {
+        for (int i=0;i<100;i++) {
+            int len = rand.nextInt(10000)+1;
+            int blockSize = rand.nextInt(10000)+1;
+            BlockDigestInputStream bdis = new BlockDigestInputStream
+                (getRandomInputStream(len),ALGORITHM,blockSize);
+            byte[] b = new byte[len];
+            new DataInputStream(bdis).readFully(b);
+            bdis.close();
+            assertEquals("Same block count len="+len+",bs="+blockSize,
+                         Util.divideCeil(len,blockSize),
+                         bdis.getBlockDigests().length);
+
+        }
+    }
+
+    public void testDigest() throws Exception {
+        for (int i=0;i<100;i++) {
+            int len = rand.nextInt(10000)+1;
+            int blockSize = len;
+            BlockDigestInputStream bdis = new BlockDigestInputStream
+                (getRandomInputStream(len),ALGORITHM,blockSize);
+            MessageDigest md = MessageDigest.getInstance(ALGORITHM);
+            DigestInputStream dis = new DigestInputStream(bdis,md);
+            byte[] b = new byte[len];
+            new DataInputStream(dis).readFully(b);
+            dis.close();
+            Buffer buf = bdis.getBlockDigests()[0];
+            assert("Equal Hashes",Util.arraysEqual(buf.b,buf.off,
+                                                   md.digest(),0,buf.len));
+        }
+    }
+
+    public static final InputStream getRandomInputStream(int len) {
+        byte[] b = new byte[len];
+        rand.nextBytes(b);
+        return new ByteArrayInputStream(b);
+    }
+}
+                

Added: trunk/contrib/fec/common/test/src/com/onionnetworks/util/BzeroTest.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/util/BzeroTest.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/util/BzeroTest.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,71 @@
+package com.onionnetworks.util;
+
+import java.util.Random;
+
+import junit.framework.*;
+
+public class BzeroTest extends TestCase {
+
+    private Random rand = new Random();
+    
+    public BzeroTest(String name) {
+       super(name);
+    }
+
+    public void testEmpty() {
+        // bzero empty arrays of various sizes.
+        for (int i=1;i<100;i++) {
+            //System.out.println(i+"/100");
+            byte[] b = new byte[rand.nextInt(i*i)+1];
+            byte[] b2 = dupArray(b);
+            int off = rand.nextInt(b.length);
+            int len = rand.nextInt(b.length-off);
+            Util.bzero(b,off,len);
+            assert("Empty: off="+off+",len="+len,checkArray(b2,b,off,len));
+        }
+    }
+    
+    public void testFilled() {
+        // bzero filled arrays of various sizes
+        for (int i=1;i<100;i++) {
+            //System.out.println(i+"/100");
+            byte[] b = createArray(rand.nextInt(i*i)+1);
+            byte[] b2 = dupArray(b);
+            int off = rand.nextInt(b.length);
+            int len = rand.nextInt(b.length-off);
+            Util.bzero(b,off,len);
+            assert("Filled : off="+off+",len="+len,checkArray(b2,b,off,len));
+        }
+    }
+    
+    public static final byte[] createArray(int len) {
+        byte[] b = new byte[len];
+        for (int i=0;i<b.length;i++) {
+            b[i] = (byte) i;
+        }
+        return b;
+    }
+
+    public static final byte[] dupArray(byte[] b) {
+        byte[] b2 = new byte[b.length];
+        System.arraycopy(b,0,b2,0,b.length);
+        return b2;
+    }
+    
+    public static final boolean checkArray(byte[] orig, byte[] b, int off, 
+                                          int len) {
+        for (int i=0;i<b.length;i++) {
+            if (i<off || i>=(off+len)) {
+                if (orig[i] != b[i]) {
+                   return false;
+               }
+            } else {
+                if (b[i] != 0) {
+                   return false;
+                }
+            }
+        }
+       return true;
+    }
+}
+                

Added: trunk/contrib/fec/common/test/src/com/onionnetworks/util/Char2Bytes.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/util/Char2Bytes.java    
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/util/Char2Bytes.java    
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,58 @@
+package com.onionnetworks.util;
+
+import java.util.Random;
+
+public class Char2Bytes {
+    static Random rand = new Random();
+
+    public static void main(String[] args) {
+        
+        // GetChars
+        for (int i=0;i<100;i++) {
+            System.out.println(i+"/100");
+           byte[] b = createByteArray(rand.nextInt(500000) * 2);
+           char[] c = Util.getChars(b);
+           checkArrays(c,b);
+        }            
+         // getBytes
+        for (int i=0;i<100;i++) {
+            System.out.println(i+"/100");
+           char[] c = createCharacterArray(rand.nextInt(100000));
+           byte[] b = Util.getBytes(c);
+           checkArrays(c,b);
+        }            
+    }
+
+    public static final char[] createCharacterArray(int len) {
+        char[] b = new char[len];
+        for (int i=0;i<b.length;i++) {
+            b[i] = (char)(rand.nextInt(Character.MAX_VALUE + 1));
+        }
+        return b;
+    }
+
+    public static final byte[] createByteArray(int len) {
+        byte[] b = new byte[len];
+       byte min = Byte.MIN_VALUE;
+        for (int i=0;i<b.length;i++) {
+            b[i] = (byte)(rand.nextInt(-2 * min) + min);
+        }
+        return b;
+    }
+
+    public static final void checkArrays(char[] chars, byte[] bytes) {
+       if (chars.length * 2 != bytes.length) {
+           System.err.println(chars.length * 2 + " != " + bytes.length);
+           throw new RuntimeException("Shit! regression!");
+       }
+        for (int i = 0; i < chars.length; i++) {
+           if ( (byte)((chars[i] & 0xFF00) >> 8) != bytes[2 * i] ) {
+               throw new RuntimeException("Shit! regression!");
+           }
+           if ( (byte)(chars[i] & 0xFF) != bytes[2 * i + 1] ) {
+               throw new RuntimeException("Shit! regression!");
+           }
+        }
+    }
+}
+                

Added: trunk/contrib/fec/common/test/src/com/onionnetworks/util/Hex2Bytes.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/util/Hex2Bytes.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/util/Hex2Bytes.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,30 @@
+package com.onionnetworks.util;
+
+import java.util.Random;
+
+public class Hex2Bytes {
+    static Random rand = new Random();
+
+    public static void main(String[] args) {
+        
+        for (int i=0;i<100;i++) {
+            System.out.println(i+"/100");
+           byte[] b = createByteArray(rand.nextInt(500000) * 2);
+            String hex = Util.bytesToHex(b);
+            if (! Util.arraysEqual(b, 0, Util.hexToBytes(hex),
+                            0, b.length)) {
+                throw new RuntimeException("Crap!: " + hex);
+            }
+        }            
+    }
+
+    public static final byte[] createByteArray(int len) {
+        byte[] b = new byte[len];
+       byte min = Byte.MIN_VALUE;
+        for (int i=0;i<b.length;i++) {
+            b[i] = (byte)(rand.nextInt(-2 * min) + min);
+        }
+        return b;
+    }
+}
+                

Added: trunk/contrib/fec/common/test/src/com/onionnetworks/util/Log2Test.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/util/Log2Test.java      
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/util/Log2Test.java      
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,23 @@
+package com.onionnetworks.util;
+
+import java.util.Random;
+
+import junit.framework.*;
+
+public class Log2Test extends TestCase {
+
+    private Random rand = new Random();
+    
+    public Log2Test(String name) {
+       super(name);
+    }
+
+    public void testLog2() {
+       for (int i=0;i<10000;i++) {
+           double a = rand.nextDouble();
+           assertEquals(Util.log2(Math.pow(2,a)),a,.00000001);
+           assertEquals(Math.pow(2,Util.log2(a)),a,.00000001);
+       }
+    }
+}
+                

Added: 
trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeSetTest.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeSetTest.java  
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeSetTest.java  
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,57 @@
+package com.onionnetworks.util;
+
+import com.onionnetworks.util.*;
+import java.util.*;
+import java.text.ParseException;
+
+import junit.framework.*;
+
+public class RangeSetTest extends TestCase {
+
+    private static Random rand = new Random();
+    
+    public RangeSetTest(String name) {
+       super(name);
+    }
+
+    public void testEmpty() {
+        RangeSet rs = new RangeSet();
+        RangeSet rs2 = new RangeSet();
+        rs2.add(new Range(true,true));
+        assertEquals(rs.complement(),rs2);
+        assertEquals(rs.complement().complement(),rs);
+    }
+
+    public void testSize() {
+        RangeSet rs = new RangeSet();
+        int size = 1000;
+        for (int i=0;i<size/2;i++) {
+            rs.add(i);
+        }
+        for (int i=0;i<size/2;i++) {
+            rs.add(i+size);
+        }
+        assertEquals(rs.size(),size);
+    }
+
+    public void testContains() {
+        int[] ints = getInts(rand.nextInt(1000));
+        RangeSet rs = new RangeSet();
+        for (int i=0;i<ints.length;i++) {
+            rs.add(ints[i]);
+        }
+        for (int i=0;i<ints.length;i++) {
+            assert(rs.contains(ints[i]));
+        }
+    }       
+          
+
+    public static final int[] getInts(int num) {
+        int[] result = new int[num];
+        for (int i=0;i<num;i++) {
+            result[i] = rand.nextInt();
+        }
+        return result;
+    }
+}
+                

Added: trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeTest.java
===================================================================
--- trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeTest.java     
2006-11-10 20:02:12 UTC (rev 10866)
+++ trunk/contrib/fec/common/test/src/com/onionnetworks/util/RangeTest.java     
2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,99 @@
+package com.onionnetworks.util;
+
+import com.onionnetworks.util.*;
+import java.util.*;
+import java.text.ParseException;
+
+import junit.framework.*;
+
+public class RangeTest extends TestCase {
+
+    private static Random rand = new Random();
+    
+    public RangeTest(String name) {
+       super(name);
+    }
+
+    public void testOneNum() {
+        int num = rand.nextInt();
+        Range r = new Range(num);
+        assertEquals(r.getMin(),num);
+        assertEquals(r.getMax(),num);
+    }
+
+    public void testMinMax() {
+        int min = rand.nextInt();
+        int max = randNotLessThan(min);
+        Range r = new Range(min,max);
+        assertEquals(r.getMin(),min);
+        assertEquals(r.getMax(),max);
+    }
+
+    public void testInf() {
+        Range r = new Range(true,true);
+        assert(r.isMinNegInf());
+        assert(r.isMaxPosInf());
+    }
+
+    public void testBadInf() {
+        try {
+            new Range(false,0);
+            fail("Should have thrown exception");
+        } catch (IllegalArgumentException e) {}
+        try {
+            new Range(0,false);
+            fail("Should have thrown exception");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testBadMinMax() {
+        try {
+            new Range(0,-10);
+            fail("Should have thrown exception");
+        } catch (IllegalArgumentException e) {}
+    }
+
+    public void testContains() {
+        Range r = new Range(-10,10);
+        assert(r.contains(0));
+        assert(r.contains(new Range(-1,1)));
+    }
+
+    public void testSize() {
+        assertEquals(new Range(1).size(),1);
+        assertEquals(new Range(-10,10).size(),21);
+        assertEquals(new Range(true,10).size(),-1);
+        assertEquals(new Range(10,true).size(),-1);
+    }
+    
+    public void testEquals() {
+        assert(new Range(0).equals(new Range(0,0)));
+        assert(!new Range(true,true).equals(new Range(Integer.MIN_VALUE,
+                                                      Integer.MAX_VALUE)));
+    }
+
+    public void testParse() throws ParseException {
+        for (int i=0;i<100;i++) {
+            int min = rand.nextInt();
+            int max = randNotLessThan(min);
+            Range r = new Range(min,max);
+            assertEquals(Range.parse(min+"-"+max),r);
+            assertEquals(Range.parse(r.toString()),r);
+        }
+
+        assertEquals(Range.parse("11"),new Range(11));
+        assertEquals(Range.parse("0-0"),new Range(0));
+        assertEquals(Range.parse("-5"),new Range(-5));
+        assertEquals(Range.parse("-10--1"),new Range(-10,-1));
+        assertEquals(Range.parse("(--5"),new Range(true,-5));
+        assertEquals(Range.parse("-5-)"),new Range(-5,true));
+        assertEquals(Range.parse("(-)"),new Range(true,true));
+    }
+
+    public static final int randNotLessThan(int num) {
+        int n;
+        while ((n = rand.nextInt()) < num) {}
+        return n;
+    }
+}
+                

Added: trunk/contrib/fec/common/tools/build.xml
===================================================================
--- trunk/contrib/fec/common/tools/build.xml    2006-11-10 20:02:12 UTC (rev 
10866)
+++ trunk/contrib/fec/common/tools/build.xml    2006-11-10 20:10:52 UTC (rev 
10867)
@@ -0,0 +1,101 @@
+<!--
+The default target is "jars".
+
+Other targets:
+
+  clean         - Remove all generated files.
+  classes       - Builds the classes.
+  jars          - Creates the jars.
+  prepare       - Set up build directory structure.
+  javadoc       - Builds the API documentation.
+  demo          - Runs the demo application.
+
+-->
+<project name="Tools" default="jars" basedir="..">
+  <property environment="env"/>
+
+  <!-- ==================================================================== -->
+  <target name="prepare">
+    <mkdir dir="${tools.javadoc}" />
+    <mkdir dir="${tools.classes}" />
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="tidy"
+         description="Remove generated files not needed for running">
+
+    <delete dir="${tools.classes}" quiet="true"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="clean" depends="tidy"
+         description="Remove generated files">
+        
+    <delete dir="${tools.javadoc}" quiet="true"/>
+    <delete file="${tools.jar}" quiet="true"/>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="classes" depends="prepare"
+   description="Compile the java classes" >
+    <copy todir="${tools.classes}">
+      <fileset dir="${tools.src}">
+        <include name="**/*.properties" />
+      </fileset>
+    </copy>
+    <javac srcdir="${tools.src}"
+           destdir="${tools.classes}"
+          classpath="${tools.classpath}"
+           debug="${javac.debug}"
+           optimize="${javac.optimize}"
+           deprecation="${javac.deprecation}"
+           >
+       <include name="**/*.java"/>
+    </javac>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="jars" depends="classes"
+         description="Build the jar files">
+    <jar jarfile="${tools.jar}" basedir="${tools.classes}"> 
+
+       <include name="${packagepath}/**"/>
+    </jar>
+  </target>
+
+  <!-- ==================================================================== -->
+  <target name="javadoc" depends="jars"
+   description="Build the javadoc">
+    <mkdir dir="${tools.javadoc}"/>
+    <javadoc packagenames="${tools.package}.*"
+             sourcepath="${tools.src}"
+             destdir="${tools.javadoc}"
+            classpath="${tools.classpath}"
+             author="true"
+             version="true"
+             public="true"
+             windowtitle="${ant.project.name} API"
+             doctitle="${ant.project.name}"
+             bottom="Copyright &#169; 2002 Onion Networks. All Rights 
Reserved.">
+      <link href="http://java.sun.com/products/jdk/1.3/docs/api/"/>
+    </javadoc>
+  </target>
+
+  <target name="demo" depends="jars" 
+   description="Build and run the demo">
+    <java classname="${tools.package}.Demo"
+          dir="${basedir}"
+         classpath="${env.CLASSPATH};${tools.classpath}"
+          fork="yes"
+          failonerror="yes"
+          >
+    </java>
+  </target>
+</project>
+
+
+
+
+
+
+

Added: 
trunk/contrib/fec/common/tools/src/com/onionnetworks/util/RateCalculatorTest.java
===================================================================
--- 
trunk/contrib/fec/common/tools/src/com/onionnetworks/util/RateCalculatorTest.java
   2006-11-10 20:02:12 UTC (rev 10866)
+++ 
trunk/contrib/fec/common/tools/src/com/onionnetworks/util/RateCalculatorTest.java
   2006-11-10 20:10:52 UTC (rev 10867)
@@ -0,0 +1,60 @@
+package com.onionnetworks.util;
+
+import java.util.Random;
+
+public class RateCalculatorTest {
+    
+    public static final int[] DOWNLOAD_RATES = new int[] {23,5,80};
+    public static final int GROUP_SIZE = 32;
+    public static final int MAX_GROUPS = 1000;
+    
+    public int eventCount;
+    public int updates;
+    public int[] counts = new int[DOWNLOAD_RATES.length];
+    public RateCalculator rc;
+    public Random rand = new Random();
+    
+    public RateCalculatorTest() throws Exception {
+       rc = new RateCalculator(30000,100,.75f);
+       new Thread(new Runnable() {
+           public void run() {
+               while (true) {
+                   try {
+                       Thread.sleep(100);
+                   } catch (InterruptedException e) {
+                   }
+                   System.out.print
+                     ("\revents="+eventCount+",est="+
+                      (int) rc.getEstimatedEventCount
+                      (MAX_GROUPS*GROUP_SIZE)+",updates="+
+                      updates+",rate="+
+                      (int) (rc.getRate()*1000)+",time="+
+                      (int) (rc.getEstimatedTimeRemaining
+                      (MAX_GROUPS*GROUP_SIZE)/1000)+"                 ");
+               }
+           }
+       }).start();
+
+       while (eventCount < MAX_GROUPS*GROUP_SIZE) {
+           Thread.sleep(1000);
+           for (int i=0;i<DOWNLOAD_RATES.length;i++) {
+               for (int j=0;j<DOWNLOAD_RATES[i];j++) {
+                   counts[i]++;
+                   eventCount++;
+                   if (counts[i] == GROUP_SIZE) {
+                       rc.update(GROUP_SIZE);
+                       updates+=GROUP_SIZE;
+                       counts[i]=0;
+                   }
+               }
+           }
+       }   
+       System.exit(0);
+    }
+    
+    public static final void main(String[] args) throws Exception {
+       new RateCalculatorTest();
+    }    
+}
+    
+    


Reply via email to