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 © 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 <junit> 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 © 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();
+ }
+}
+
+