summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2006-08-30 02:09:37 +0000
committerBrian S. O'Neill <bronee@gmail.com>2006-08-30 02:09:37 +0000
commit41e062b7896bfae5ecd1d75f7b99305b7b2e6ce8 (patch)
treecc38efe346de67519892cca24caf83805b0d4e2e /src
parent21ffac73f08766ea06a289028f7479d04cb0ccff (diff)
Add general utilities
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/amazon/carbonado/util/AnnotationBuilder.java206
-rw-r--r--src/main/java/com/amazon/carbonado/util/AnnotationDescParser.java315
-rw-r--r--src/main/java/com/amazon/carbonado/util/AnnotationDescPrinter.java281
-rw-r--r--src/main/java/com/amazon/carbonado/util/AnnotationPrinter.java331
-rw-r--r--src/main/java/com/amazon/carbonado/util/AnnotationVisitor.java496
-rw-r--r--src/main/java/com/amazon/carbonado/util/Appender.java38
-rw-r--r--src/main/java/com/amazon/carbonado/util/BelatedCreator.java430
-rw-r--r--src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java197
-rw-r--r--src/main/java/com/amazon/carbonado/util/TaskQueueThread.java179
-rw-r--r--src/main/java/com/amazon/carbonado/util/Throttle.java138
-rw-r--r--src/main/java/com/amazon/carbonado/util/ThrowUnchecked.java291
-rw-r--r--src/main/java/com/amazon/carbonado/util/package-info.java23
12 files changed, 2925 insertions, 0 deletions
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;