From 41e062b7896bfae5ecd1d75f7b99305b7b2e6ce8 Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" 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') 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 { + + // Stack for building arrays of annotations. + private Stack mStack; + + public AnnotationBuilder() { + super(false); + mStack = new Stack(); + } + + 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 mvList = new ArrayList(); + 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 { + /** + * Returns an annotation descriptor that has no parameters. + */ + public static String makePlainDescriptor(Class 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 { + 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 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 { + private final Comparator 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 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; iNote: 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 { + private static final String REF_FIELD_NAME = "ref"; + + private static final Map, Class> cWrapperCache; + + private static final ExecutorService cThreadPool; + + static { + cWrapperCache = new SoftValuedHashMap(); + cThreadPool = Executors.newCachedThreadPool(new TFactory()); + } + + private final Class mType; + final int mMinRetryDelayMillis; + + private T mReal; + private boolean mFailed; + private Throwable mFailedError; + private T mBogus; + private AtomicReference 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 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(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 getWrapper() { + Class clazz; + synchronized (cWrapperCache) { + clazz = (Class) 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 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 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; iQuickConstructorGenerator 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> + @SuppressWarnings("unchecked") + private static Map, Map, 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. + * + *

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. + * + *

+     * public interface StringFactory {
+     *     String newEmptyString();
+     *
+     *     String newStringFromChars(char[] chars);
+     *
+     *     String newStringFromBytes(byte[] bytes, String charsetName)
+     *         throws UnsupportedEncodingException;
+     * }
+     * 
+ * + * Here's an example if it being used: + * + *
+     * StringFactory sf = QuickConstructorGenerator.getInstance(String.class, StringFactory.class);
+     * ...
+     * String str = sf.newStringFromChars(new char[] {'h', 'e', 'l', 'l', 'o'});
+     * 
+ * + * @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 getInstance(Class objectType, Class factory) { + Map, 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 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(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. + * + *

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. + * + *

Example: + * + *

+ * public <E extends Throwable> 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);
+ * }
+ * 
+ * + * @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