From 41e062b7896bfae5ecd1d75f7b99305b7b2e6ce8 Mon Sep 17 00:00:00 2001
From: "Brian S. O'Neill" <bronee@gmail.com>
Date: Wed, 30 Aug 2006 02:09:37 +0000
Subject: Add general utilities

---
 .../amazon/carbonado/util/AnnotationBuilder.java   | 206 +++++++++
 .../carbonado/util/AnnotationDescParser.java       | 315 +++++++++++++
 .../carbonado/util/AnnotationDescPrinter.java      | 281 ++++++++++++
 .../amazon/carbonado/util/AnnotationPrinter.java   | 331 ++++++++++++++
 .../amazon/carbonado/util/AnnotationVisitor.java   | 496 +++++++++++++++++++++
 .../java/com/amazon/carbonado/util/Appender.java   |  38 ++
 .../com/amazon/carbonado/util/BelatedCreator.java  | 430 ++++++++++++++++++
 .../carbonado/util/QuickConstructorGenerator.java  | 197 ++++++++
 .../com/amazon/carbonado/util/TaskQueueThread.java | 179 ++++++++
 .../java/com/amazon/carbonado/util/Throttle.java   | 138 ++++++
 .../com/amazon/carbonado/util/ThrowUnchecked.java  | 291 ++++++++++++
 .../com/amazon/carbonado/util/package-info.java    |  23 +
 12 files changed, 2925 insertions(+)
 create mode 100644 src/main/java/com/amazon/carbonado/util/AnnotationBuilder.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/AnnotationDescParser.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/AnnotationDescPrinter.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/AnnotationPrinter.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/AnnotationVisitor.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/Appender.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/BelatedCreator.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/TaskQueueThread.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/Throttle.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/ThrowUnchecked.java
 create mode 100644 src/main/java/com/amazon/carbonado/util/package-info.java

(limited to 'src/main/java/com/amazon/carbonado')

diff --git a/src/main/java/com/amazon/carbonado/util/AnnotationBuilder.java b/src/main/java/com/amazon/carbonado/util/AnnotationBuilder.java
new file mode 100644
index 0000000..9b5b900
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/AnnotationBuilder.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.util.Stack;
+
+import org.cojen.classfile.TypeDesc;
+import org.cojen.classfile.attribute.Annotation;
+
+/**
+ * Builds all Annotation properties to a Cojen Annotation definition.
+ *
+ * @author Brian S O'Neill
+ */
+public class AnnotationBuilder extends AnnotationVisitor<Object, Annotation> {
+
+    // Stack for building arrays of annotations.
+    private Stack<Annotation.MemberValue[]> mStack;
+
+    public AnnotationBuilder() {
+        super(false);
+        mStack = new Stack<Annotation.MemberValue[]>();
+    }
+
+    public Object visit(String name, int pos, java.lang.annotation.Annotation value,
+                        Annotation ann)
+    {
+        if (name == null && mStack.size() == 0) {
+            // Root annotation.
+            super.visit(name, pos, value, ann);
+        } else {
+            // Nested annotation.
+            Annotation nested = ann.makeAnnotation();
+            nested.setType(TypeDesc.forClass(value.annotationType()));
+            super.visit(name, pos, value, nested);
+            put(ann, name, pos, ann.makeMemberValue(nested));
+        }
+        return null;
+    }
+
+    public Object visit(String name, int pos, int value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, long value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, float value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, double value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, boolean value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, byte value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, short value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, char value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, String value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(value));
+        return null;
+    }
+
+    public Object visit(String name, int pos, Class value, Annotation ann) {
+        put(ann, name, pos, ann.makeMemberValue(TypeDesc.forClass(value)));
+        return null;
+    }
+
+    public Object visit(String name, int pos, Enum value, Annotation ann) {
+        put(ann, name, pos,
+            ann.makeMemberValue(TypeDesc.forClass(value.getDeclaringClass()), value.name()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, java.lang.annotation.Annotation[] value,
+                        Annotation ann)
+    {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, int[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, long[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, float[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, double[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, boolean[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, byte[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, short[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, char[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, String[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, Class[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    public Object visit(String name, int pos, Enum[] value, Annotation ann) {
+        mStack.push(new Annotation.MemberValue[value.length]);
+        super.visit(name, pos, value, ann);
+        put(ann, name, pos, ann.makeMemberValue(mStack.pop()));
+        return null;
+    }
+
+    private void put(Annotation ann, String name, int pos, Annotation.MemberValue mv) {
+        if (name == null) {
+            mStack.peek()[pos] = mv;
+        } else {
+            ann.putMemberValue(name, mv);
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/AnnotationDescParser.java b/src/main/java/com/amazon/carbonado/util/AnnotationDescParser.java
new file mode 100644
index 0000000..09871d9
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/AnnotationDescParser.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.cojen.classfile.TypeDesc;
+import org.cojen.classfile.attribute.Annotation;
+
+// Import the tags.
+import static com.amazon.carbonado.util.AnnotationDescPrinter.*;
+
+/**
+ * Parses an annotation descriptor String to a Cojen Annotation definition.
+ *
+ * @author Brian S O'Neill
+ * @see AnnotationDescPrinter
+ */
+public class AnnotationDescParser {
+    private final String mStr;
+
+    private int mPos;
+
+    /**
+     * @param annotationString annotation to parse
+     */
+    public AnnotationDescParser(String annotationString) {
+        mStr = annotationString;
+    }
+
+    /**
+     * Parses the given annotation, returning the root annotation that received
+     * the results.
+     *
+     * @param rootAnnotation root annotation
+     * @return root annotation
+     * @throws IllegalArgumentExcecption if annotation is malformed
+     */
+    public Annotation parse(Annotation rootAnnotation) {
+        mPos = 0;
+
+        if (parseTag() != TAG_ANNOTATION) {
+            throw error("Malformed");
+        }
+
+        TypeDesc rootAnnotationType = parseTypeDesc();
+
+        if (rootAnnotation == null) {
+            rootAnnotation = buildRootAnnotation(rootAnnotationType);
+        } else if (!rootAnnotationType.equals(rootAnnotation.getType())) {
+            throw new IllegalArgumentException
+                ("Annotation type of \"" + rootAnnotationType +
+                 "\" does not match expected type of \"" + rootAnnotation.getType());
+        }
+
+        parseAnnotation(rootAnnotation, rootAnnotationType);
+
+        return rootAnnotation;
+    }
+
+    /**
+     * Override this method if a root annotation is not provided, as it must be
+     * built after parsing the root annotation type. By default, this method
+     * throws UnsupportedOperationException.
+     */
+    protected Annotation buildRootAnnotation(TypeDesc rootAnnotationType) {
+        throw new UnsupportedOperationException();
+    }
+
+    private void parseAnnotation(Annotation dest, TypeDesc annType) {
+        while (true) {
+            try {
+                if (mStr.charAt(mPos) == ';') {
+                    mPos++;
+                    break;
+                }
+            } catch (IndexOutOfBoundsException e) {
+                error("Missing terminator");
+            }
+
+            if (mPos >= mStr.length()) {
+                break;
+            }
+
+            String propName = parseName();
+            char propTag = peekTag();
+
+            Annotation.MemberValue mv;
+            if (propTag == TAG_ARRAY) {
+                mPos++;
+                mv = parseArray(dest, peekTag(), parseTypeDesc());
+            } else {
+                mv = parseProperty(dest, propTag, parseTypeDesc());
+            }
+
+            dest.putMemberValue(propName, mv);
+        }
+    }
+
+    private Annotation.MemberValue parseArray(Annotation dest, char compTag, TypeDesc compType) {
+        List<Annotation.MemberValue> mvList = new ArrayList<Annotation.MemberValue>();
+        while (true) {
+            try {
+                if (mStr.charAt(mPos) == ';') {
+                    mPos++;
+                    break;
+                }
+            } catch (IndexOutOfBoundsException e) {
+                error("Missing terminator");
+            }
+
+            mvList.add(parseProperty(dest, compTag, compType));
+        }
+        Annotation.MemberValue[] mvArray = new Annotation.MemberValue[mvList.size()];
+        return dest.makeMemberValue(mvList.toArray(mvArray));
+    }
+
+    private Annotation.MemberValue parseProperty(Annotation dest, char propTag, TypeDesc propType)
+    {
+        Annotation.MemberValue mv;
+
+        try {
+            if (propTag == TAG_STRING) {
+                StringBuilder b = new StringBuilder();
+                while (true) {
+                    char c = mStr.charAt(mPos++);
+                    if (c == ';') {
+                        break;
+                    }
+                    if (c == '\\') {
+                        c = mStr.charAt(mPos++);
+                    }
+                    b.append(c);
+                }
+                mv = dest.makeMemberValue(b.toString());
+            } else if (propTag == TAG_BOOLEAN) {
+                mv = dest.makeMemberValue(mStr.charAt(mPos++) != '0');
+            } else if (propTag == TAG_CHAR) {
+                mv = dest.makeMemberValue(mStr.charAt(mPos++));
+            } else if (propTag == TAG_ANNOTATION) {
+                Annotation propAnn = dest.makeAnnotation();
+                propAnn.setType(propType);
+                parseAnnotation(propAnn, propType);
+                mv = dest.makeMemberValue(propAnn);
+            } else if (propTag == TAG_CLASS) {
+                mv = dest.makeMemberValue(parseTypeDesc());
+            } else {
+                int endPos = nextTerminator();
+                String valueStr = mStr.substring(mPos, endPos);
+
+                switch (propTag) {
+                default:
+                    throw error("Invalid tag");
+
+                case TAG_BYTE: {
+                    mv = dest.makeMemberValue(Byte.parseByte(valueStr));
+                    break;
+                }
+
+                case TAG_SHORT: {
+                    mv = dest.makeMemberValue(Short.parseShort(valueStr));
+                    break;
+                }
+
+                case TAG_INT: {
+                    mv = dest.makeMemberValue(Integer.parseInt(valueStr));
+                    break;
+                }
+
+                case TAG_LONG: {
+                    mv = dest.makeMemberValue(Long.parseLong(valueStr));
+                    break;
+                }
+
+                case TAG_FLOAT: {
+                    mv = dest.makeMemberValue(Float.parseFloat(valueStr));
+                    break;
+                }
+
+                case TAG_DOUBLE: {
+                    mv = dest.makeMemberValue(Double.parseDouble(valueStr));
+                    break;
+                }
+
+                case TAG_ENUM: {
+                    mv = dest.makeMemberValue(propType, valueStr);
+                    break;
+                }
+                }
+
+                mPos = endPos + 1;
+            }
+        } catch (IndexOutOfBoundsException e) {
+            throw error("Invalid property value");
+        }
+
+        return mv;
+    }
+
+    private TypeDesc parseTypeDesc() {
+        try {
+            int endPos;
+
+            switch (mStr.charAt(mPos)) {
+            default:
+                throw error("Invalid tag");
+
+            case TAG_ARRAY:
+                mPos++;
+                return parseTypeDesc().toArrayType();
+
+            case TAG_STRING:
+                mPos++;
+                return TypeDesc.STRING;
+
+            case TAG_CLASS:
+                mPos++;
+                return TypeDesc.forClass(Class.class);
+
+            case TAG_ANNOTATION:
+                mPos++;
+                return parseTypeDesc();
+
+            case TAG_ENUM:
+                mPos++;
+                endPos = nextTerminator();
+                int dot = mStr.lastIndexOf('.', endPos);
+                if (dot < mPos) {
+                    throw error("Invalid enumeration");
+                }
+                TypeDesc type = TypeDesc.forClass(mStr.substring(mPos, dot));
+                mPos = dot + 1;
+                return type;
+
+            case TAG_BOOLEAN:
+            case TAG_BYTE:
+            case TAG_SHORT:
+            case TAG_CHAR:
+            case TAG_INT:
+            case TAG_LONG:
+            case TAG_FLOAT:
+            case TAG_DOUBLE:
+            case TAG_VOID:
+                endPos = mPos + 1;
+                break;
+
+            case TAG_OBJECT:
+                endPos = nextTerminator() + 1;
+                break;
+            }
+
+            TypeDesc type = TypeDesc.forDescriptor(mStr.substring(mPos, endPos));
+            mPos = endPos;
+
+            return type;
+        } catch (IndexOutOfBoundsException e) {
+            throw error("Invalid type descriptor");
+        }
+    }
+
+    private char peekTag() {
+        char tag = parseTag();
+        mPos--;
+        return tag;
+    }
+
+    private char parseTag() {
+        try {
+            return mStr.charAt(mPos++);
+        } catch (IndexOutOfBoundsException e) {
+            throw error("Too short");
+        }
+    }
+
+    private String parseName() {
+        int index = mStr.indexOf('=', mPos);
+        if (index < 0) {
+            throw error("Incomplete assignment");
+        }
+        String name = mStr.substring(mPos, index);
+        mPos = index + 1;
+        return name;
+    }
+
+    private int nextTerminator() {
+        int index = mStr.indexOf(';', mPos);
+        if (index < 0) {
+            throw error("Missing terminator");
+        }
+        return index;
+    }
+
+    private IllegalArgumentException error(String message) {
+        message = "Illegal annotation descriptor: " + message +
+            " at position " + mPos + " of " + mStr;
+        return new IllegalArgumentException(message);
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/AnnotationDescPrinter.java b/src/main/java/com/amazon/carbonado/util/AnnotationDescPrinter.java
new file mode 100644
index 0000000..4f80911
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/AnnotationDescPrinter.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.lang.annotation.Annotation;
+
+import org.cojen.classfile.TypeDesc;
+
+/**
+ * Prints machine readable, self-describing, annotation descriptors.
+ *
+ * @author Brian S O'Neill
+ * @see AnnotationDescParser
+ */
+public class AnnotationDescPrinter extends AnnotationVisitor<Object, Object> {
+    /**
+     * Returns an annotation descriptor that has no parameters.
+     */
+    public static String makePlainDescriptor(Class<? extends Annotation> annotationType) {
+        return "" + TAG_ANNOTATION + TypeDesc.forClass(annotationType).getDescriptor();
+    }
+
+    /**
+     * Returns an annotation descriptor that has no parameters.
+     */
+    public static String makePlainDescriptor(String annotationType) {
+        return "" + TAG_ANNOTATION + TAG_OBJECT + annotationType.replace('.', '/') + ';';
+    }
+
+    static final char
+        TAG_BOOLEAN    = 'Z',
+        TAG_BYTE       = 'B',
+        TAG_SHORT      = 'S',
+        TAG_CHAR       = 'C',
+        TAG_INT        = 'I',
+        TAG_LONG       = 'J',
+        TAG_FLOAT      = 'F',
+        TAG_DOUBLE     = 'D',
+        TAG_VOID       = 'V',
+        TAG_OBJECT     = 'L',
+        TAG_ARRAY      = '[',
+        TAG_STRING     = 's',
+        TAG_CLASS      = 'c',
+        TAG_ENUM       = 'e',
+        TAG_ANNOTATION = '@';
+
+    private final StringBuilder mBuilder;
+
+    /**
+     * @param sort when true, sort annotation members by name (case sensitive)
+     * @param b StringBuilder to get printed results
+     */
+    public AnnotationDescPrinter(boolean sort, StringBuilder b) {
+        super(sort);
+        mBuilder = b;
+    }
+
+    /**
+     * Prints the annotation to the builder passed to the constructor.
+     *
+     * @param value Annotation to visit
+     * @return null
+     */
+    public Object visit(Annotation value) {
+        return visit(value, null);
+    }
+
+    public Object visit(String name, int pos, Annotation value, Object param) {
+        if (appendName(name, pos, TAG_ANNOTATION)) {
+            mBuilder.append(TypeDesc.forClass(value.annotationType()).getDescriptor());
+        }
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, int value, Object param) {
+        appendName(name, pos, TAG_INT);
+        mBuilder.append(value);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, long value, Object param) {
+        appendName(name, pos, TAG_LONG);
+        mBuilder.append(value);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, float value, Object param) {
+        appendName(name, pos, TAG_FLOAT);
+        mBuilder.append(Float.toString(value));
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, double value, Object param) {
+        appendName(name, pos, TAG_DOUBLE);
+        mBuilder.append(Double.toString(value));
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, boolean value, Object param) {
+        appendName(name, pos, TAG_BOOLEAN);
+        mBuilder.append(value ? '1' : '0');
+        return null;
+    }
+
+    public Object visit(String name, int pos, byte value, Object param) {
+        appendName(name, pos, TAG_BYTE);
+        mBuilder.append(value);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, short value, Object param) {
+        appendName(name, pos, TAG_SHORT);
+        mBuilder.append(value);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, char value, Object param) {
+        appendName(name, pos, TAG_CHAR);
+        mBuilder.append(value);
+        return null;
+    }
+
+    public Object visit(String name, int pos, String value, Object param) {
+        appendName(name, pos, TAG_STRING);
+        int length = value.length();
+        for (int i=0; i<length; i++) {
+            char c = value.charAt(i);
+            if (c == '\\' || c == ';') {
+                mBuilder.append('\\');
+            }
+            mBuilder.append(c);
+        }
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Class value, Object param) {
+        appendName(name, pos, TAG_CLASS);
+        if (value == String.class) {
+            mBuilder.append(TAG_STRING);
+        } else if (value == Class.class) {
+            mBuilder.append(TAG_CLASS);
+        } else {
+            mBuilder.append(TypeDesc.forClass(value).getDescriptor());
+        }
+        return null;
+    }
+
+    public Object visit(String name, int pos, Enum value, Object param) {
+        if (appendName(name, pos, TAG_ENUM)) {
+            mBuilder.append(value.getDeclaringClass().getName());
+            mBuilder.append('.');
+        }
+        mBuilder.append(value.name());
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Annotation[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, int[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, long[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, float[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, double[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, boolean[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, byte[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, short[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, char[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, String[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Class[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Enum[] value, Object param) {
+        appendName(name, pos, TAG_ARRAY);
+        super.visit(name, pos, value, param);
+        mBuilder.append(';');
+        return null;
+    }
+
+    /**
+     * @return true if tag was appended too
+     */
+    private boolean appendName(String name, int pos, char tag) {
+        if (name != null) {
+            mBuilder.append(name);
+            mBuilder.append('=');
+        }
+        if (name != null || pos == 0) {
+            mBuilder.append(tag);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/AnnotationPrinter.java b/src/main/java/com/amazon/carbonado/util/AnnotationPrinter.java
new file mode 100644
index 0000000..df811c0
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/AnnotationPrinter.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Prints an Annotation into a parseable format, exactly the same as Java
+ * Annotation syntax.
+ *
+ * @author Brian S O'Neill
+ */
+public class AnnotationPrinter extends AnnotationVisitor<Object, Object> {
+    private final StringBuilder mBuilder;
+
+    /**
+     * @param sort when true, sort annotation members by name (case sensitive)
+     * @param b StringBuilder to get printed results
+     */
+    public AnnotationPrinter(boolean sort, StringBuilder b) {
+        super(sort);
+        mBuilder = b;
+    }
+
+    /**
+     * Prints the annotation to the builder passed to the constructor.
+     *
+     * @param value Annotation to visit
+     * @return null
+     */
+    public Object visit(Annotation value) {
+        return visit(value, null);
+    }
+
+    public Object visit(String name, int pos, Annotation value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('@');
+        mBuilder.append(value.annotationType().getName());
+        mBuilder.append('(');
+        super.visit(name, pos, value, param);
+        mBuilder.append(')');
+        return null;
+    }
+
+    public Object visit(String name, int pos, int value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value);
+        return null;
+    }
+
+    public Object visit(String name, int pos, long value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value);
+        mBuilder.append('L');
+        return null;
+    }
+
+    public Object visit(String name, int pos, float value, Object param) {
+        appendName(name, pos);
+        if (Float.isNaN(value)) {
+            mBuilder.append("java.lang.Float.NaN");
+        } else if (value == Float.POSITIVE_INFINITY) {
+            mBuilder.append("java.lang.Float.POSITIVE_INFINITY");
+        } else if (value == Float.NEGATIVE_INFINITY) {
+            mBuilder.append("java.lang.Float.NEGATIVE_INFINITY");
+        } else {
+            mBuilder.append(value);
+            mBuilder.append('f');
+        }
+        return null;
+    }
+
+    public Object visit(String name, int pos, double value, Object param) {
+        appendName(name, pos);
+        if (Double.isNaN(value)) {
+            mBuilder.append("java.lang.Double.NaN");
+        } else if (value == Double.POSITIVE_INFINITY) {
+            mBuilder.append("java.lang.Double.POSITIVE_INFINITY");
+        } else if (value == Double.NEGATIVE_INFINITY) {
+            mBuilder.append("java.lang.Double.NEGATIVE_INFINITY");
+        } else {
+            mBuilder.append(value);
+            mBuilder.append('d');
+        }
+        return null;
+    }
+
+    public Object visit(String name, int pos, boolean value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value);
+        return null;
+    }
+
+    public Object visit(String name, int pos, byte value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value);
+        return null;
+    }
+
+    public Object visit(String name, int pos, short value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value);
+        return null;
+    }
+
+    public Object visit(String name, int pos, char value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('\'');
+        appendChar(value, false);
+        mBuilder.append('\'');
+        return null;
+    }
+
+    private void appendChar(char c, boolean forString) {
+        switch (c) {
+        case '\b':
+        case '\t':
+        case '\n':
+        case '\f':
+        case '\r':
+        case '\"':
+        case '\'':
+        case '\\':
+            if (forString) {
+                if (c != '\'') {
+                    mBuilder.append('\\');
+                }
+            } else if (c != '\"') {
+                mBuilder.append('\\');
+            }
+
+            char e = c;
+
+            switch (c) {
+            case '\b':
+                e = 'b';
+                break;
+            case '\t':
+                e = 't';
+                break;
+            case '\n':
+                e = 'n';
+                break;
+            case '\f':
+                e = 'f';
+                break;
+            case '\r':
+                e = 'r';
+                break;
+            }
+
+            mBuilder.append(e);
+            break;
+
+        default:
+            if (c >= 32 && c <= 126) {
+                if (forString) {
+                    if (c == '"') {
+                        mBuilder.append('\\');
+                    }
+                } else {
+                    if (c == '\'') {
+                        mBuilder.append('\\');
+                    }
+                }
+                mBuilder.append(c);
+                break;
+            }
+
+            mBuilder.append('\\');
+            mBuilder.append('u');
+            if (c < 0x1000) {
+                mBuilder.append('0');
+                if (c < 0x100) {
+                    mBuilder.append('0');
+                    if (c < 0x10) {
+                        mBuilder.append('0');
+                    }
+                }
+            }
+            mBuilder.append(Integer.toHexString(c));
+        }
+    }
+
+    public Object visit(String name, int pos, String value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('"');
+        int length = value.length();
+        for (int i=0; i<length; i++) {
+            appendChar(value.charAt(i), true);
+        }
+        mBuilder.append('"');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Class value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value.getName());
+        mBuilder.append(".class");
+        return null;
+    }
+
+    public Object visit(String name, int pos, Enum value, Object param) {
+        appendName(name, pos);
+        mBuilder.append(value.getDeclaringClass().getName());
+        mBuilder.append('.');
+        mBuilder.append(value.name());
+        return null;
+    }
+
+    public Object visit(String name, int pos, Annotation[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, int[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, long[] value, Object param) {
+        appendName(name, pos);
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, float[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, double[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, boolean[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, byte[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, short[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, char[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, String[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Class[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    public Object visit(String name, int pos, Enum[] value, Object param) {
+        appendName(name, pos);
+        mBuilder.append('{');
+        super.visit(name, pos, value, param);
+        mBuilder.append('}');
+        return null;
+    }
+
+    private void appendName(String name, int pos) {
+        if (pos > 0) {
+            mBuilder.append(", ");
+        }
+        if (name != null) {
+            mBuilder.append(name);
+            mBuilder.append('=');
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/AnnotationVisitor.java b/src/main/java/com/amazon/carbonado/util/AnnotationVisitor.java
new file mode 100644
index 0000000..d68bad9
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/AnnotationVisitor.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.cojen.util.BeanComparator;
+
+/**
+ * Generic annotation visitor. Override methods to capture specific elements.
+ *
+ * @author Brian S O'Neill
+ */
+public class AnnotationVisitor<R, P> {
+    private final Comparator<Method> mMethodComparator;
+
+    /**
+     * @param sort when true, sort annotation members by name (case sensitive)
+     */
+    public AnnotationVisitor(boolean sort) {
+        if (sort) {
+            mMethodComparator = BeanComparator
+                .forClass(Method.class).orderBy("name").caseSensitive();
+        } else {
+            mMethodComparator = null;
+        }
+    }
+
+    /**
+     * Visits an annotation by breaking it down into its components and calling
+     * various other visit methods.
+     *
+     * @param value Initial Annotation to visit
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public final R visit(Annotation value, P param) {
+        return visit(null, 0, value, param);
+    }
+
+    /**
+     * Visits an annotation by breaking it down into its components and calling
+     * various other visit methods.
+     *
+     * @param name member name, or null if array member or not part of an annotation
+     * @param pos position of member in list or array
+     * @param value Annotation visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, Annotation value, P param) {
+        Class<? extends Annotation> annotationType = value.annotationType();
+
+        Method[] methods = annotationType.getMethods();
+
+        if (mMethodComparator != null) {
+            Arrays.sort(methods, mMethodComparator);
+        }
+
+        int i = 0;
+        for (Method m : methods) {
+            if (m.getDeclaringClass() != annotationType || m.getParameterTypes().length > 0) {
+                continue;
+            }
+
+            Class propType = m.getReturnType();
+            if (propType == void.class) {
+                continue;
+            }
+
+            String propName = m.getName();
+            Object propValue;
+            try {
+                propValue = m.invoke(value);
+            } catch (Exception e) {
+                ThrowUnchecked.fireRootCause(e);
+                return null;
+            }
+
+            if (propType.isArray()) {
+                if (propType.isPrimitive()) {
+                    propType = propType.getComponentType();
+                    if (propType == int.class) {
+                        visit(propName, i, (int[]) propValue, param);
+                    } else if (propType == long.class) {
+                        visit(propName, i, (long[]) propValue, param);
+                    } else if (propType == float.class) {
+                        visit(propName, i, (float[]) propValue, param);
+                    } else if (propType == double.class) {
+                        visit(propName, i, (double[]) propValue, param);
+                    } else if (propType == boolean.class) {
+                        visit(propName, i, (boolean[]) propValue, param);
+                    } else if (propType == byte.class) {
+                        visit(propName, i, (byte[]) propValue, param);
+                    } else if (propType == short.class) {
+                        visit(propName, i, (short[]) propValue, param);
+                    } else if (propType == char.class) {
+                        visit(propName, i, (char[]) propValue, param);
+                    }
+                } else if (propValue instanceof String[]) {
+                    visit(propName, i, (String[]) propValue, param);
+                } else if (propValue instanceof Class[]) {
+                    visit(propName, i, (Class[]) propValue, param);
+                } else if (propValue instanceof Enum[]) {
+                    visit(propName, i, (Enum[]) propValue, param);
+                } else if (propValue instanceof Annotation[]) {
+                    visit(propName, i, (Annotation[]) propValue, param);
+                }
+            } else if (propType.isPrimitive()) {
+                if (propType == int.class) {
+                    visit(propName, i, (Integer) propValue, param);
+                } else if (propType == long.class) {
+                    visit(propName, i, (Long) propValue, param);
+                } else if (propType == float.class) {
+                    visit(propName, i, (Float) propValue, param);
+                } else if (propType == double.class) {
+                    visit(propName, i, (Double) propValue, param);
+                } else if (propType == boolean.class) {
+                    visit(propName, i, (Boolean) propValue, param);
+                } else if (propType == byte.class) {
+                    visit(propName, i, (Byte) propValue, param);
+                } else if (propType == short.class) {
+                    visit(propName, i, (Short) propValue, param);
+                } else if (propType == char.class) {
+                    visit(propName, i, (Character) propValue, param);
+                }
+            } else if (propValue instanceof String) {
+                visit(propName, i, (String) propValue, param);
+            } else if (propValue instanceof Class) {
+                visit(propName, i, (Class) propValue, param);
+            } else if (propValue instanceof Enum) {
+                visit(propName, i, (Enum) propValue, param);
+            } else if (propValue instanceof Annotation) {
+                visit(propName, i, (Annotation) propValue, param);
+            }
+
+            i++;
+        }
+
+        return null;
+    }
+
+    /**
+     * Override to visit ints.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value int visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, int value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit longs.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value long visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, long value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit floats.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value float visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, float value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit doubles.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value double visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, double value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit booleans.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value boolean visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, boolean value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit bytes.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value byte visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, byte value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit shorts.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value short visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, short value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit chars.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value char visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, char value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit Strings.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value String visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, String value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit Classes.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value Class visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, Class value, P param) {
+        return null;
+    }
+
+    /**
+     * Override to visit Enums.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value Enum visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, Enum value, P param) {
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value Annotation array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, Annotation[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value int array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, int[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value long array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, long[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value float array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, float[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value double array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, double[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value boolean array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, boolean[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value byte array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, byte[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value short array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, short[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value char array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, char[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value String array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, String[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value Class array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, Class[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+
+    /**
+     * Visits each array element.
+     *
+     * @param name member name, or null if array member
+     * @param pos position of member in list or array
+     * @param value Enum array visited
+     * @param param custom parameter
+     * @return custom result, null by default
+     */
+    public R visit(String name, int pos, Enum[] value, P param) {
+        for (int i=0; i<value.length; i++) {
+            visit(null, i, value[i], param);
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/Appender.java b/src/main/java/com/amazon/carbonado/util/Appender.java
new file mode 100644
index 0000000..03a3559
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/Appender.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.io.IOException;
+
+/**
+ * Interface that supports an alternate way of providing a string representation
+ * for an object.
+ *
+ * @author Brian S O'Neill
+ */
+public interface Appender {
+    /**
+     * Append the string representation of this object to the given Appendable.
+     *
+     * @param appendable Appendable object to receive string representation
+     * @throws IOException if thrown from given Appendable
+     * @throws NullPointerException if appendable is null
+     */
+    void appendTo(Appendable appendable) throws IOException;
+}
diff --git a/src/main/java/com/amazon/carbonado/util/BelatedCreator.java b/src/main/java/com/amazon/carbonado/util/BelatedCreator.java
new file mode 100644
index 0000000..4655c0e
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/BelatedCreator.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.Map;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.Label;
+import org.cojen.classfile.MethodDesc;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+
+import org.cojen.util.ClassInjector;
+import org.cojen.util.SoftValuedHashMap;
+
+/**
+ * Generic one-shot factory which supports late object creation. If the object
+ * creation results in an exception or is taking too long, the object produced
+ * instead is a bogus one. After retrying, if the real object is created, then
+ * the bogus object turns into a wrapper to the real object.
+ *
+ * <p>Note: If a bogus object is created, the wrapper cannot always be a drop-in
+ * replacement for the real object. If the wrapper is cloned, it won't have the
+ * same behavior as cloning the real object. Also, synchronizing on the wrapper
+ * will not synchronize the real object.
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class BelatedCreator<T, E extends Exception> {
+    private static final String REF_FIELD_NAME = "ref";
+
+    private static final Map<Class<?>, Class<?>> cWrapperCache;
+
+    private static final ExecutorService cThreadPool;
+
+    static {
+        cWrapperCache = new SoftValuedHashMap();
+        cThreadPool = Executors.newCachedThreadPool(new TFactory());
+    }
+
+    private final Class<T> mType;
+    final int mMinRetryDelayMillis;
+
+    private T mReal;
+    private boolean mFailed;
+    private Throwable mFailedError;
+    private T mBogus;
+    private AtomicReference<T> mRef;
+
+    private CreateThread mCreateThread;
+
+    /**
+     * @param type type of object created
+     * @param minRetryDelayMillis minimum milleseconds to wait before retrying
+     * to create object after failure; if negative, never retry
+     * @throws IllegalArgumentException if type is null or is not an interface
+     */
+    protected BelatedCreator(Class<T> type, int minRetryDelayMillis) {
+        if (type == null) {
+            throw new IllegalArgumentException("Type is null");
+        }
+        if (!type.isInterface()) {
+            throw new IllegalArgumentException("Type must be an interface: " + type);
+        }
+        mType = type;
+        mMinRetryDelayMillis = minRetryDelayMillis;
+    }
+
+    /**
+     * Returns real or bogus object. If real object is returned, then future
+     * invocations of this method return the same real object instance. This
+     * method waits for the real object to be created, if it is blocked. If
+     * real object creation fails immediately, then this method will not wait,
+     * returning a bogus object immediately instead.
+     *
+     * @param timeoutMillis maximum time to wait for real object before
+     * returning bogus one; if negative, potentially wait forever
+     * @throws E exception thrown from createReal
+     */
+    public synchronized T get(final int timeoutMillis) throws E {
+        if (mReal != null) {
+            return mReal;
+        }
+
+        if (mBogus != null && mMinRetryDelayMillis < 0) {
+            return mBogus;
+        }
+
+        if (mCreateThread == null) {
+            mCreateThread = new CreateThread();
+            cThreadPool.submit(mCreateThread);
+        }
+
+        if (timeoutMillis != 0) {
+            final long start = System.nanoTime();
+
+            try {
+                if (timeoutMillis < 0) {
+                    while (mReal == null && mCreateThread != null) {
+                        if (timeoutMillis < 0) {
+                            wait();
+                        }
+                    }
+                } else {
+                    long remaining = timeoutMillis;
+                    while (mReal == null && mCreateThread != null && !mFailed) {
+                        wait(remaining);
+                        long elapsed = (System.nanoTime() - start) / 1000000L;
+                        if ((remaining -= elapsed) <= 0) {
+                            break;
+                        }
+                    }
+                }
+            } catch (InterruptedException e) {
+            }
+
+            if (mReal != null) {
+                return mReal;
+            }
+
+            long elapsed = (System.nanoTime() - start) / 1000000L;
+
+            if (elapsed >= timeoutMillis) {
+                timedOutNotification(elapsed);
+            }
+        }
+
+        if (mFailedError != null) {
+            // Take ownership of error. Also stitch traces together for
+            // context. This gives the illusion that the creation attempt
+            // occurred in this thread.
+
+            Throwable error = mFailedError;
+            mFailedError = null;
+
+            StackTraceElement[] trace = error.getStackTrace();
+            error.fillInStackTrace();
+            StackTraceElement[] localTrace = error.getStackTrace();
+            StackTraceElement[] completeTrace =
+                new StackTraceElement[trace.length + localTrace.length];
+            System.arraycopy(trace, 0, completeTrace, 0, trace.length);
+            System.arraycopy(localTrace, 0, completeTrace, trace.length, localTrace.length);
+            error.setStackTrace(completeTrace);
+
+            ThrowUnchecked.fire(error);
+        }
+
+        if (mBogus == null) {
+            mRef = new AtomicReference<T>(createBogus());
+            try {
+                mBogus = getWrapper().newInstance(mRef);
+            } catch (Exception e) {
+                ThrowUnchecked.fire(e);
+            }
+        }
+
+        return mBogus;
+    }
+
+    /**
+     * Create instance of real object. If there is a recoverable error creating
+     * the object, return null. Any error logging must be performed by the
+     * implementation of this method. If null is returned, expect this method
+     * to be called again in the future.
+     *
+     * @return real object, or null if there was a recoverable error
+     * @throws E unrecoverable error
+     */
+    protected abstract T createReal() throws E;
+
+    /**
+     * Create instance of bogus object.
+     */
+    protected abstract T createBogus();
+
+    /**
+     * Notification that createReal is taking too long. This can be used to log
+     * a message.
+     *
+     * @param timedOutMillis milliseconds waited before giving up
+     */
+    protected abstract void timedOutNotification(long timedOutMillis);
+
+    /**
+     * Notification that createReal has produced the real object. The default
+     * implementation does nothing.
+     */
+    protected void createdNotification(T object) {
+    }
+
+    synchronized void created(T object) {
+        mReal = object;
+        if (mBogus != null) {
+            mBogus = null;
+            if (mRef != null) {
+                // Changing reference to real object. The ref object is also
+                // held by the auto-generated wrapper, so changing here changes
+                // the wrapper's behavior.
+                mRef.set(object);
+            }
+        }
+        mFailed = false;
+        notifyAll();
+        createdNotification(object);
+    }
+
+    synchronized void failed() {
+        if (mReal == null) {
+            mFailed = true;
+        }
+        notifyAll();
+    }
+
+    /**
+     * @param error optional error to indicate thread is exiting because of this
+     */
+    synchronized void handleThreadExit(Throwable error) {
+        if (mReal == null) {
+            mFailed = true;
+            if (error != null) {
+                mFailedError = error;
+            }
+        }
+        mCreateThread = null;
+        notifyAll();
+    }
+
+    /**
+     * Returns a Constructor that accepts an AtomicReference to the wrapped
+     * object.
+     */
+    private Constructor<T> getWrapper() {
+        Class<T> clazz;
+        synchronized (cWrapperCache) {
+            clazz = (Class<T>) cWrapperCache.get(mType);
+            if (clazz == null) {
+                clazz = createWrapper();
+                cWrapperCache.put(mType, clazz);
+            }
+        }
+
+        try {
+            return clazz.getConstructor(AtomicReference.class);
+        } catch (NoSuchMethodException e) {
+            ThrowUnchecked.fire(e);
+            return null;
+        }
+    }
+
+    private Class<T> createWrapper() {
+        ClassInjector ci = ClassInjector.create();
+        ClassFile cf = new ClassFile(ci.getClassName());
+        cf.addInterface(mType);
+        cf.markSynthetic();
+        cf.setSourceFile(BelatedCreator.class.getName());
+        cf.setTarget("1.5");
+
+        final TypeDesc atomicRefType = TypeDesc.forClass(AtomicReference.class);
+
+        cf.addField(Modifiers.PRIVATE.toFinal(true), REF_FIELD_NAME, atomicRefType);
+
+        CodeBuilder b = new CodeBuilder(cf.addConstructor(Modifiers.PUBLIC,
+                                                          new TypeDesc[] {atomicRefType}));
+        b.loadThis();
+        b.invokeSuperConstructor(null);
+        b.loadThis();
+        b.loadLocal(b.getParameter(0));
+        b.storeField(REF_FIELD_NAME, atomicRefType);
+        b.returnVoid();
+
+        // Now define all interface methods to call wrapped object.
+
+        for (Method m : mType.getMethods()) {
+            try {
+                Object.class.getMethod(m.getName(), m.getParameterTypes());
+                // Defined in object too, so skip it for now
+                continue;
+            } catch (NoSuchMethodException e) {
+            }
+
+            addWrappedCall(cf, new CodeBuilder(cf.addMethod(m)), m);
+        }
+
+        // Also wrap non-final public methods from Object. mType is an
+        // interface, so we don't have to worry about any superclasses --
+        // except Object.  We want to make sure that all (non-final) Object
+        // methods delegate to the generated proxy.  For example, one would
+        // expect toString to call the wrapped object, not the wrapper itself.
+
+        for (Method m : Object.class.getMethods()) {
+            int modifiers = m.getModifiers();
+            if (!Modifier.isFinal(modifiers) && Modifier.isPublic(modifiers)) {
+                b = new CodeBuilder
+                    (cf.addMethod(Modifiers.PUBLIC, m.getName(), MethodDesc.forMethod(m)));
+                addWrappedCall(cf, b, m);
+            }
+        }
+
+        Class<T> clazz = ci.defineClass(cf);
+        return clazz;
+    }
+
+    private void addWrappedCall(ClassFile cf, CodeBuilder b, Method m) {
+        // Special behavior for equals method
+        boolean isEqualsMethod = false;
+        if (m.getName().equals("equals") && m.getReturnType().equals(boolean.class)) {
+            Class[] paramTypes = m.getParameterTypes();
+            isEqualsMethod = paramTypes.length == 1 && paramTypes[0].equals(Object.class);
+        }
+
+        if (isEqualsMethod) {
+            b.loadThis();
+            b.loadLocal(b.getParameter(0));
+            Label notEqual = b.createLabel();
+            b.ifEqualBranch(notEqual, false);
+            b.loadConstant(true);
+            b.returnValue(TypeDesc.BOOLEAN);
+
+            notEqual.setLocation();
+
+            // Check if object is our type.
+            b.loadLocal(b.getParameter(0));
+            b.instanceOf(cf.getType());
+            Label isInstance = b.createLabel();
+            b.ifZeroComparisonBranch(isInstance, "!=");
+
+            b.loadConstant(false);
+            b.returnValue(TypeDesc.BOOLEAN);
+
+            isInstance.setLocation();
+        }
+
+        final TypeDesc atomicRefType = TypeDesc.forClass(AtomicReference.class);
+
+        // Load wrapped object...
+        b.loadThis();
+        b.loadField(REF_FIELD_NAME, atomicRefType);
+        b.invokeVirtual(atomicRefType, "get", TypeDesc.OBJECT, null);
+        b.checkCast(TypeDesc.forClass(mType));
+
+        // Load parameters...
+        for (int i=0; i<b.getParameterCount(); i++) {
+            b.loadLocal(b.getParameter(i));
+        }
+
+        // Special behavior for equals method
+        if (isEqualsMethod) {
+            // Extract wrapped object.
+            b.checkCast(cf.getType());
+            b.loadField(REF_FIELD_NAME, atomicRefType);
+            b.invokeVirtual(atomicRefType, "get", TypeDesc.OBJECT, null);
+            b.checkCast(TypeDesc.forClass(mType));
+        }
+
+        // Invoke wrapped method...
+        b.invoke(m);
+
+        if (m.getReturnType() == void.class) {
+            b.returnVoid();
+        } else {
+            b.returnValue(TypeDesc.forClass(m.getReturnType()));
+        }
+    }
+
+    private class CreateThread implements Runnable {
+        public void run() {
+            try {
+                while (true) {
+                    T real = createReal();
+                    if (real != null) {
+                        created(real);
+                        break;
+                    }
+                    failed();
+                    if (mMinRetryDelayMillis < 0) {
+                        break;
+                    }
+                    try {
+                        Thread.sleep(mMinRetryDelayMillis);
+                    } catch (InterruptedException e) {
+                        break;
+                    }
+                }
+                handleThreadExit(null);
+            } catch (Throwable e) {
+                handleThreadExit(e);
+            }
+        }
+    }
+
+    private static class TFactory implements ThreadFactory {
+        private static int cCount;
+
+        private static synchronized int nextID() {
+            return ++cCount;
+        }
+
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(r);
+            t.setDaemon(true);
+            t.setName("BelatedCreator-" + nextID());
+            return t;
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java b/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java
new file mode 100644
index 0000000..c0ff885
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Map;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.util.ClassInjector;
+import org.cojen.util.WeakIdentityMap;
+import org.cojen.util.SoftValuedHashMap;
+
+/**
+ * Generates code to invoke constructors. This is a replacement for {@link
+ * java.lang.reflect.Constructor} which is easier to use and performs
+ * better. In one tested situation, overall performance was improved by about
+ * 10%.
+ *
+ * <p>QuickConstructorGenerator is not general purpose however, as the
+ * parameters to the constructor must be known, and the constructor must be
+ * public. It is intended to be used for constructing instances of
+ * auto-generated classes. The exact parameters may be known at compile time,
+ * but the actual object type is not.
+ *
+ * @author Brian S O'Neill
+ */
+public class QuickConstructorGenerator {
+    // Map<factory class, Map<object type, factory instance>>
+    @SuppressWarnings("unchecked")
+    private static Map<Class<?>, Map<Class<?>, Object>> cCache = new WeakIdentityMap();
+
+    /**
+     * Returns a factory instance for one type of object. Each method in the
+     * interface defines a constructor via its parameters. Any checked
+     * exceptions declared thrown by the constructor must also be declared by
+     * the method. The method return types can be the same type as the
+     * constructed object or a supertype.
+     *
+     * <p>Here is a contrived example for constructing strings. In practice,
+     * such a string factory is is useless, since the "new" operator can be
+     * invoked directly.
+     *
+     * <pre>
+     * public interface StringFactory {
+     *     String newEmptyString();
+     *
+     *     String newStringFromChars(char[] chars);
+     *
+     *     String newStringFromBytes(byte[] bytes, String charsetName)
+     *         throws UnsupportedEncodingException;
+     * }
+     * </pre>
+     *
+     * Here's an example if it being used:
+     *
+     * <pre>
+     * StringFactory sf = QuickConstructorGenerator.getInstance(String.class, StringFactory.class);
+     * ...
+     * String str = sf.newStringFromChars(new char[] {'h', 'e', 'l', 'l', 'o'});
+     * </pre>
+     *
+     * @param objectType type of object to construct
+     * @param factory interface defining which objects can be constructed
+     * @throws IllegalArgumentException if factory type is not an interface or
+     * if it is malformed
+     */
+    @SuppressWarnings("unchecked")
+    public static synchronized <F> F getInstance(Class<?> objectType, Class<F> factory) {
+        Map<Class<?>, Object> innerCache = cCache.get(factory);
+        if (innerCache == null) {
+            innerCache = new SoftValuedHashMap();
+            cCache.put(factory, innerCache);
+        }
+        F instance = (F) innerCache.get(objectType);
+        if (instance != null) {
+            return instance;
+        }
+
+        if (objectType == null) {
+            throw new IllegalArgumentException("No object type");
+        }
+        if (factory == null) {
+            throw new IllegalArgumentException("No factory type");
+        }
+        if (!factory.isInterface()) {
+            throw new IllegalArgumentException("Factory must be an interface");
+        }
+
+        String prefix = objectType.getName();
+        if (prefix.startsWith("java.")) {
+            // Defining classes in java packages is restricted.
+            int index = prefix.lastIndexOf('.');
+            if (index > 0) {
+                prefix = prefix.substring(index + 1);
+            }
+        }
+        ClassInjector ci = ClassInjector.create(prefix, objectType.getClassLoader());
+
+        ClassFile cf = null;
+
+        for (Method method : factory.getMethods()) {
+            if (!Modifier.isAbstract(method.getModifiers())) {
+                continue;
+            }
+
+            Constructor ctor;
+            try {
+                ctor = objectType.getConstructor((Class[]) method.getParameterTypes());
+            } catch (NoSuchMethodException e) {
+                throw new IllegalArgumentException(e);
+            }
+
+            if (!method.getReturnType().isAssignableFrom(objectType)) {
+                throw new IllegalArgumentException
+                    ("Method return type must be \"" +
+                     objectType.getName() + "\" or supertype: " + method);
+            }
+
+            Class<?>[] methodExTypes = method.getExceptionTypes();
+
+            for (Class<?> ctorExType : ctor.getExceptionTypes()) {
+                if (RuntimeException.class.isAssignableFrom(ctorExType) ||
+                    Error.class.isAssignableFrom(ctorExType)) {
+                    continue;
+                }
+                exCheck: {
+                    // Make sure method declares throwing it or a supertype.
+                    for (Class<?> methodExType : methodExTypes) {
+                        if (methodExType.isAssignableFrom(ctorExType)) {
+                            break exCheck;
+                        }
+                    }
+                    throw new IllegalArgumentException("Method must declare throwing \"" +
+                                                       ctorExType.getName() +"\": " + method);
+                }
+            }
+
+            if (cf == null) {
+                cf = new ClassFile(ci.getClassName());
+                cf.setSourceFile(QuickConstructorGenerator.class.getName());
+                cf.setTarget("1.5");
+                cf.addInterface(factory);
+                cf.markSynthetic();
+                cf.addDefaultConstructor();
+            }
+
+            // Now define the method that constructs the object.
+            CodeBuilder b = new CodeBuilder(cf.addMethod(method));
+            b.newObject(TypeDesc.forClass(objectType));
+            b.dup();
+            int count = b.getParameterCount();
+            for (int i=0; i<count; i++) {
+                b.loadLocal(b.getParameter(i));
+            }
+            b.invoke(ctor);
+            b.returnValue(TypeDesc.OBJECT);
+        }
+
+        if (cf == null) {
+            // No methods found to implement.
+            throw new IllegalArgumentException("No methods in factory to implement");
+        }
+
+        try {
+            instance = (F) ci.defineClass(cf).newInstance();
+        } catch (IllegalAccessException e) {
+            throw new UndeclaredThrowableException(e);
+        } catch (InstantiationException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+
+        innerCache.put(objectType, instance);
+
+        return instance;
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/TaskQueueThread.java b/src/main/java/com/amazon/carbonado/util/TaskQueueThread.java
new file mode 100644
index 0000000..51febc0
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/TaskQueueThread.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Simple generic queue for running tasks from a single thread. Be sure to call
+ * shutdown or interrupt when done using the thread, or else it will never exit.
+ *
+ * @author Brian S O'Neill
+ */
+public class TaskQueueThread extends Thread implements Executor {
+    private static final int
+        STATE_NOT_STARTED = 0,
+        STATE_RUNNING = 1,
+        STATE_SHOULD_STOP = 2,
+        STATE_STOPPED = 3;
+
+    private static final Runnable STOP_TASK = new Runnable() {public void run() {}};
+
+    private final BlockingQueue<Runnable> mQueue;
+    private final long mTimeoutMillis;
+
+    private int mState = STATE_NOT_STARTED;
+
+    /**
+     * @param name name to give this thread
+     * @param queueSize fixed size of queue
+     */
+    public TaskQueueThread(String name, int queueSize) {
+        this(name, queueSize, 0);
+    }
+
+    /**
+     * @param name name to give this thread
+     * @param queueSize fixed size of queue
+     * @param timeoutMillis default maximum time to wait for queue to have an available slot
+     */
+    public TaskQueueThread(String name, int queueSize, long timeoutMillis) {
+        super(name);
+        mQueue = new ArrayBlockingQueue<Runnable>(queueSize, true);
+        mTimeoutMillis = timeoutMillis;
+    }
+
+    /**
+     * Enqueue a task to run.
+     *
+     * @param task task to enqueue
+     * @throws RejectedExecutionException if wait interrupted, timeout expires,
+     * or shutdown has been called
+     */
+    public void execute(Runnable task) throws RejectedExecutionException {
+        execute(task, mTimeoutMillis);
+    }
+
+    /**
+     * Enqueue a task to run.
+     *
+     * @param task task to enqueue
+     * @param timeoutMillis maximum time to wait for queue to have an available slot
+     * @throws RejectedExecutionException if wait interrupted, timeout expires,
+     * or shutdown has been called
+     */
+    public void execute(Runnable task, long timeoutMillis) throws RejectedExecutionException {
+        if (task == null) {
+            throw new NullPointerException("Cannot accept null task");
+        }
+        synchronized (this) {
+            if (mState != STATE_RUNNING && mState != STATE_NOT_STARTED) {
+                throw new RejectedExecutionException("Task queue is shutdown");
+            }
+        }
+        try {
+            if (!mQueue.offer(task, timeoutMillis, TimeUnit.MILLISECONDS)) {
+                throw new RejectedExecutionException("Unable to enqueue task after waiting " +
+                                                     timeoutMillis + " milliseconds");
+            }
+        } catch (InterruptedException e) {
+            throw new RejectedExecutionException(e);
+        }
+    }
+
+    /**
+     * Indicate that this task queue thread should finish running its enqueued
+     * tasks and then exit. Enqueueing new tasks will result in a
+     * RejectedExecutionException being thrown. Join on this thread to wait for
+     * it to exit.
+     */
+    public synchronized void shutdown() {
+        if (mState == STATE_STOPPED) {
+            return;
+        }
+        if (mState == STATE_NOT_STARTED) {
+            mState = STATE_STOPPED;
+            return;
+        }
+        mState = STATE_SHOULD_STOP;
+        // Inject stop task into the queue so it knows to stop, in case we're blocked.
+        mQueue.offer(STOP_TASK);
+    }
+
+    public void run() {
+        synchronized (this) {
+            if (mState == STATE_SHOULD_STOP || mState == STATE_STOPPED) {
+                return;
+            }
+            if (mState == STATE_RUNNING) {
+                throw new IllegalStateException("Already running");
+            }
+            mState = STATE_RUNNING;
+        }
+
+        try {
+            while (true) {
+                boolean isStopping;
+                synchronized (this) {
+                    isStopping = mState != STATE_RUNNING;
+                }
+
+                Runnable task;
+                if (isStopping) {
+                    // Poll the queue so this thread doesn't block when it
+                    // should be stopping.
+                    task = mQueue.poll();
+                } else {
+                    try {
+                        task = mQueue.take();
+                    } catch (InterruptedException e) {
+                        break;
+                    }
+                }
+
+                if (task == null || task == STOP_TASK) {
+                    // Marker to indicate we should stop.
+                    break;
+                }
+
+                try {
+                    task.run();
+                } catch (ThreadDeath e) {
+                    throw e;
+                } catch (Throwable e) {
+                    try {
+                        Thread t = Thread.currentThread();
+                        t.getUncaughtExceptionHandler().uncaughtException(t, e);
+                    } catch (Throwable e2) {
+                        // If there is an exception reporting the exception, throw the original.
+                        ThrowUnchecked.fire(e);
+                    }
+                }
+            }
+        } finally {
+            synchronized (this) {
+                mState = STATE_STOPPED;
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/Throttle.java b/src/main/java/com/amazon/carbonado/util/Throttle.java
new file mode 100644
index 0000000..fe5a8a8
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/Throttle.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+/**
+ * General purpose class for throttling work relative to its actual measured
+ * performance. To throttle a task, call the throttle method each time a unit
+ * of work has been performed. It computes a rolling average for the amount of
+ * time it takes to perform some work, and then it sleeps a calculated amount
+ * of time to throttle back.
+ *
+ * <p>Instances are intended for use by one thread, and so they are not
+ * thread-safe.
+ *
+ * @author Brian S O'Neill
+ */
+public class Throttle {
+    private final double[] mWorkTimeSamples;
+
+    // Next index in mSamples to use.
+    private int mSampleIndex;
+
+    private double mWorkTimeSum;
+
+    // Amount of samples gathered.
+    private int mSampleCount;
+
+    private long mLastTimestampNanos;
+
+    private double mSleepRequiredNanos;
+
+    /**
+     * @param windowSize amount of samples to keep in the rolling average
+     */
+    public Throttle(int windowSize) {
+        if (windowSize < 1) {
+            throw new IllegalArgumentException();
+        }
+
+        mWorkTimeSamples = new double[windowSize];
+    }
+
+    /**
+     * @param desiredSpeed 1.0 = perform work at full speed,
+     * 0.5 = perform work at half speed, 0.0 = fully suspend work
+     * @param sleepPrecisionMillis sleep precision, in milliseconds. Typical
+     * value is 10 to 100 milliseconds.
+     */
+    public void throttle(double desiredSpeed, long sleepPrecisionMillis)
+        throws InterruptedException
+    {
+        long timestampNanos = System.nanoTime();
+
+        int sampleCount = mSampleCount;
+
+        int index = mSampleIndex;
+        double workTime = timestampNanos - mLastTimestampNanos;
+        double workTimeSum = mWorkTimeSum + workTime;
+
+        double[] workTimeSamples = mWorkTimeSamples;
+
+        if (sampleCount >= workTimeSamples.length) {
+            workTimeSum -= workTimeSamples[index];
+            double average = workTimeSum / sampleCount;
+
+            double sleepTimeNanos = (average / desiredSpeed) - average;
+            double sleepRequiredNanos = mSleepRequiredNanos + sleepTimeNanos;
+
+            if (sleepRequiredNanos > 0.0) {
+                double sleepRequiredMillis = sleepRequiredNanos * (1.0 / 1000000);
+
+                long millis;
+                if (sleepRequiredMillis > Long.MAX_VALUE) {
+                    millis = Long.MAX_VALUE;
+                } else {
+                    millis = Math.max(sleepPrecisionMillis, (long) sleepRequiredMillis);
+                }
+
+                Thread.sleep(millis);
+
+                long nextNanos = System.nanoTime();
+
+                // Subtract off time spent in this method, including sleep.
+                sleepRequiredNanos -= (nextNanos - timestampNanos);
+                timestampNanos = nextNanos;
+            }
+
+            mSleepRequiredNanos = sleepRequiredNanos;
+        }
+
+        workTimeSamples[index] = workTime;
+        index++;
+        if (index >= workTimeSamples.length) {
+            index = 0;
+        }
+        mSampleIndex = index;
+
+        mWorkTimeSum = workTimeSum;
+
+        if (sampleCount < workTimeSamples.length) {
+            mSampleCount = sampleCount + 1;
+        }
+
+        mLastTimestampNanos = timestampNanos;
+    }
+
+    /**
+     * Test program which exercises the CPU in an infinite loop, throttled by
+     * the amount given in args[0]. On a machine performing no other work, the
+     * average CPU load should be about the same as the throttled speed.
+     *
+     * @param args args[0] - desired speed, 0.0 to 1.0
+     */
+    public static void main(String[] args) throws Exception {
+        Throttle t = new Throttle(50);
+        double desiredSpeed = Double.parseDouble(args[0]);
+        while (true) {
+            new java.util.Date().toString();
+            t.throttle(desiredSpeed, 100);
+        }
+    }
+}
diff --git a/src/main/java/com/amazon/carbonado/util/ThrowUnchecked.java b/src/main/java/com/amazon/carbonado/util/ThrowUnchecked.java
new file mode 100644
index 0000000..748670c
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/ThrowUnchecked.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.amazon.carbonado.util;
+
+import java.lang.reflect.UndeclaredThrowableException;
+
+import org.cojen.classfile.ClassFile;
+import org.cojen.classfile.CodeBuilder;
+import org.cojen.classfile.Modifiers;
+import org.cojen.classfile.TypeDesc;
+import org.cojen.util.ClassInjector;
+
+/**
+ * Allows exceptions to be thrown which aren't declared to be thrown. Use of
+ * this technique can cause confusion since it violates the Java language rules
+ * for undeclared checked exceptions. For this reason, this class should not be
+ * used except under special circumstances such as to work around compiler
+ * bugs. An exception can be made, if calling any of the fireDeclared methods
+ * and the set of declared types matches what the caller is allowed to throw.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * public &lt;E extends Throwable&gt; void someMethod(E exception) throws E {
+ *     ...
+ *
+ *     // Apparent compiler bug sometimes disallows this. Doesn't appear to
+ *     // show up when compiling source files individually.
+ *
+ *     //throw exception;
+ *
+ *     // Throw it this way instead, and compiler doesn't know.
+ *     ThrowUnchecked.fire(exception);
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+public abstract class ThrowUnchecked {
+    private static volatile ThrowUnchecked mImpl;
+
+    /**
+     * Throws the given exception, even though it may be checked. This method
+     * only returns normally if the exception is null.
+     *
+     * @param t exception to throw
+     */
+    public static void fire(Throwable t) {
+        if (t != null) {
+            // Don't need to do anything special for unchecked exceptions.
+            if (t instanceof RuntimeException) {
+                throw (RuntimeException) t;
+            }
+            if (t instanceof Error) {
+                throw (Error) t;
+            }
+
+            ThrowUnchecked impl = mImpl;
+            if (impl == null) {
+                synchronized (ThrowUnchecked.class) {
+                    impl = mImpl;
+                    if (impl == null) {
+                        mImpl = impl = generateImpl();
+                    }
+                }
+            }
+
+            impl.doFire(t);
+        }
+    }
+
+    /**
+     * Throws the given exception if it is unchecked or an instance of any of
+     * the given declared types. Otherwise, it is thrown as an
+     * UndeclaredThrowableException. This method only returns normally if the
+     * exception is null.
+     *
+     * @param t exception to throw
+     * @param declaredTypes if exception is checked and is not an instance of
+     * any of these types, then it is thrown as an
+     * UndeclaredThrowableException.
+     */
+    public static void fireDeclared(Throwable t, Class... declaredTypes) {
+        if (t != null) {
+            if (declaredTypes != null) {
+                for (Class declaredType : declaredTypes) {
+                    if (declaredType.isInstance(t)) {
+                        fire(t);
+                    }
+                }
+            }
+            if (t instanceof RuntimeException) {
+                throw (RuntimeException) t;
+            }
+            if (t instanceof Error) {
+                throw (Error) t;
+            }
+            throw new UndeclaredThrowableException(t);
+        }
+    }
+
+    /**
+     * Throws the either the original exception or the first found cause if it
+     * matches one of the given declared types or is unchecked. Otherwise, the
+     * original exception is thrown as an UndeclaredThrowableException. This
+     * method only returns normally if the exception is null.
+     *
+     * @param t exception whose cause is to be thrown
+     * @param declaredTypes if exception is checked and is not an instance of
+     * any of these types, then it is thrown as an
+     * UndeclaredThrowableException.
+     */
+    public static void fireFirstDeclared(Throwable t, Class... declaredTypes) {
+        Throwable cause = t;
+        while (cause != null) {
+            cause = cause.getCause();
+            if (cause == null) {
+                break;
+            }
+            if (declaredTypes != null) {
+                for (Class declaredType : declaredTypes) {
+                    if (declaredType.isInstance(cause)) {
+                        fire(cause);
+                    }
+                }
+            }
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            if (cause instanceof Error) {
+                throw (Error) cause;
+            }
+        }
+        throw new UndeclaredThrowableException(t);
+    }
+
+    /**
+     * Throws the cause of the given exception, even though it may be
+     * checked. If the cause is null, then the original exception is
+     * thrown. This method only returns normally if the exception is null.
+     *
+     * @param t exception whose cause is to be thrown
+     */
+    public static void fireCause(Throwable t) {
+        if (t != null) {
+            Throwable cause = t.getCause();
+            if (cause == null) {
+                cause = t;
+            }
+            fire(cause);
+        }
+    }
+
+    /**
+     * Throws the cause of the given exception if it is unchecked or an
+     * instance of any of the given declared types. Otherwise, it is thrown as
+     * an UndeclaredThrowableException. If the cause is null, then the original
+     * exception is thrown. This method only returns normally if the exception
+     * is null.
+     *
+     * @param t exception whose cause is to be thrown
+     * @param declaredTypes if exception is checked and is not an instance of
+     * any of these types, then it is thrown as an
+     * UndeclaredThrowableException.
+     */
+    public static void fireDeclaredCause(Throwable t, Class... declaredTypes) {
+        if (t != null) {
+            Throwable cause = t.getCause();
+            if (cause == null) {
+                cause = t;
+            }
+            fireDeclared(cause, declaredTypes);
+        }
+    }
+
+    /**
+     * Throws the first found cause that matches one of the given declared
+     * types or is unchecked. Otherwise, the immediate cause is thrown as an
+     * UndeclaredThrowableException. If the immediate cause is null, then the
+     * original exception is thrown. This method only returns normally if the
+     * exception is null.
+     *
+     * @param t exception whose cause is to be thrown
+     * @param declaredTypes if exception is checked and is not an instance of
+     * any of these types, then it is thrown as an
+     * UndeclaredThrowableException.
+     */
+    public static void fireFirstDeclaredCause(Throwable t, Class... declaredTypes) {
+        Throwable cause = t;
+        while (cause != null) {
+            cause = cause.getCause();
+            if (cause == null) {
+                break;
+            }
+            if (declaredTypes != null) {
+                for (Class declaredType : declaredTypes) {
+                    if (declaredType.isInstance(cause)) {
+                        fire(cause);
+                    }
+                }
+            }
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            if (cause instanceof Error) {
+                throw (Error) cause;
+            }
+        }
+        fireDeclaredCause(t, declaredTypes);
+    }
+
+    /**
+     * Throws the root cause of the given exception, even though it may be
+     * checked. If the root cause is null, then the original exception is
+     * thrown. This method only returns normally if the exception is null.
+     *
+     * @param t exception whose root cause is to be thrown
+     */
+    public static void fireRootCause(Throwable t) {
+        Throwable root = t;
+        while (root != null) {
+            Throwable cause = root.getCause();
+            if (cause == null) {
+                break;
+            }
+            root = cause;
+        }
+        fire(root);
+    }
+
+    /**
+     * Throws the root cause of the given exception if it is unchecked or an
+     * instance of any of the given declared types. Otherwise, it is thrown as
+     * an UndeclaredThrowableException. If the root cause is null, then the
+     * original exception is thrown. This method only returns normally if the
+     * exception is null.
+     *
+     * @param t exception whose root cause is to be thrown
+     * @param declaredTypes if exception is checked and is not an instance of
+     * any of these types, then it is thrown as an
+     * UndeclaredThrowableException.
+     */
+    public static void fireDeclaredRootCause(Throwable t, Class... declaredTypes) {
+        Throwable root = t;
+        while (root != null) {
+            Throwable cause = root.getCause();
+            if (cause == null) {
+                break;
+            }
+            root = cause;
+        }
+        fireDeclared(root, declaredTypes);
+    }
+
+    private static ThrowUnchecked generateImpl() {
+        ClassInjector ci = ClassInjector.create();
+        ClassFile cf = new ClassFile(ci.getClassName(), ThrowUnchecked.class);
+        cf.addDefaultConstructor();
+        CodeBuilder b = new CodeBuilder
+            (cf.addMethod(Modifiers.PROTECTED, "doFire",
+                          null, new TypeDesc[] {TypeDesc.forClass(Throwable.class)}));
+        b.loadLocal(b.getParameter(0));
+        b.throwObject();
+        try {
+            return (ThrowUnchecked) ci.defineClass(cf).newInstance();
+        } catch (Exception e) {
+            throw new Error(e);
+        }
+    }
+
+    protected ThrowUnchecked() {
+    }
+
+    protected abstract void doFire(Throwable t);
+}
diff --git a/src/main/java/com/amazon/carbonado/util/package-info.java b/src/main/java/com/amazon/carbonado/util/package-info.java
new file mode 100644
index 0000000..7fc66f6
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/util/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utilities used by Carbonado but which have no dependencies on
+ * Carbonado. These utilities are candidates for moving into another project.
+ */
+package com.amazon.carbonado.util;
-- 
cgit v1.2.3