diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2015-07-15 23:21:27 +0200 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2015-07-15 23:21:27 +0200 |
commit | da46d30e80e4c59a41cf52055d06faa1dbb7e383 (patch) | |
tree | 52b707fbbccd5b6100088913f32c1cbd00568790 /spring-core/src/main/java/org | |
parent | c03c348db4e91c613982cbe6c99d0cf04ea14fe3 (diff) |
Imported Upstream version 4.0.9
Diffstat (limited to 'spring-core/src/main/java/org')
226 files changed, 23150 insertions, 4935 deletions
diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java new file mode 100644 index 00000000..5f48e993 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java @@ -0,0 +1,169 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A visitor to visit a Java annotation. The methods of this class must be + * called in the following order: ( <tt>visit</tt> | <tt>visitEnum</tt> | + * <tt>visitAnnotation</tt> | <tt>visitArray</tt> )* <tt>visitEnd</tt>. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public abstract class AnnotationVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The annotation visitor to which this visitor must delegate method calls. + * May be null. + */ + protected AnnotationVisitor av; + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public AnnotationVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param av + * the annotation visitor to which this visitor must delegate + * method calls. May be null. + */ + public AnnotationVisitor(final int api, final AnnotationVisitor av) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.av = av; + } + + /** + * Visits a primitive value of the annotation. + * + * @param name + * the value name. + * @param value + * the actual value, whose type must be {@link Byte}, + * {@link Boolean}, {@link Character}, {@link Short}, + * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type} or OBJECT or ARRAY sort. This + * value can also be an array of byte, boolean, short, char, int, + * long, float or double values (this is equivalent to using + * {@link #visitArray visitArray} and visiting each array element + * in turn, but is more convenient). + */ + public void visit(String name, Object value) { + if (av != null) { + av.visit(name, value); + } + } + + /** + * Visits an enumeration value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the enumeration class. + * @param value + * the actual enumeration value. + */ + public void visitEnum(String name, String desc, String value) { + if (av != null) { + av.visitEnum(name, desc, value); + } + } + + /** + * Visits a nested annotation value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or + * <tt>null</tt> if this visitor is not interested in visiting this + * nested annotation. <i>The nested annotation value must be fully + * visited before calling other methods on this annotation + * visitor</i>. + */ + public AnnotationVisitor visitAnnotation(String name, String desc) { + if (av != null) { + return av.visitAnnotation(name, desc); + } + return null; + } + + /** + * Visits an array value of the annotation. Note that arrays of primitive + * types (such as byte, boolean, short, char, int, long, float or double) + * can be passed as value to {@link #visit visit}. This is what + * {@link ClassReader} does. + * + * @param name + * the value name. + * @return a visitor to visit the actual array value elements, or + * <tt>null</tt> if this visitor is not interested in visiting these + * values. The 'name' parameters passed to the methods of this + * visitor are ignored. <i>All the array values must be visited + * before calling other methods on this annotation visitor</i>. + */ + public AnnotationVisitor visitArray(String name) { + if (av != null) { + return av.visitArray(name); + } + return null; + } + + /** + * Visits the end of the annotation. + */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java new file mode 100644 index 00000000..dcd88357 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java @@ -0,0 +1,371 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * An {@link AnnotationVisitor} that generates annotations in bytecode form. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter extends AnnotationVisitor { + + /** + * The class writer to which this annotation must be added. + */ + private final ClassWriter cw; + + /** + * The number of values in this annotation. + */ + private int size; + + /** + * <tt>true<tt> if values are named, <tt>false</tt> otherwise. Annotation + * writers used for annotation default and annotation arrays use unnamed + * values. + */ + private final boolean named; + + /** + * The annotation values in bytecode form. This byte vector only contains + * the values themselves, i.e. the number of values must be stored as a + * unsigned short just before these bytes. + */ + private final ByteVector bv; + + /** + * The byte vector to be used to store the number of values of this + * annotation. See {@link #bv}. + */ + private final ByteVector parent; + + /** + * Where the number of values of this annotation must be stored in + * {@link #parent}. + */ + private final int offset; + + /** + * Next annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter next; + + /** + * Previous annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter prev; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param cw + * the class writer to which this annotation must be added. + * @param named + * <tt>true<tt> if values are named, <tt>false</tt> otherwise. + * @param bv + * where the annotation values must be stored. + * @param parent + * where the number of annotation values must be stored. + * @param offset + * where in <tt>parent</tt> the number of annotation values must + * be stored. + */ + AnnotationWriter(final ClassWriter cw, final boolean named, + final ByteVector bv, final ByteVector parent, final int offset) { + super(Opcodes.ASM5); + this.cw = cw; + this.named = named; + this.bv = bv; + this.parent = parent; + this.offset = offset; + } + + // ------------------------------------------------------------------------ + // Implementation of the AnnotationVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + if (value instanceof String) { + bv.put12('s', cw.newUTF8((String) value)); + } else if (value instanceof Byte) { + bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int v = ((Boolean) value).booleanValue() ? 1 : 0; + bv.put12('Z', cw.newInteger(v).index); + } else if (value instanceof Character) { + bv.put12('C', cw.newInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + bv.put12('S', cw.newInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + bv.put12('c', cw.newUTF8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] v = (byte[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('B', cw.newInteger(v[i]).index); + } + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] v = (short[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('S', cw.newInteger(v[i]).index); + } + } else if (value instanceof char[]) { + char[] v = (char[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('C', cw.newInteger(v[i]).index); + } + } else if (value instanceof int[]) { + int[] v = (int[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('I', cw.newInteger(v[i]).index); + } + } else if (value instanceof long[]) { + long[] v = (long[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('J', cw.newLong(v[i]).index); + } + } else if (value instanceof float[]) { + float[] v = (float[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('F', cw.newFloat(v[i]).index); + } + } else if (value instanceof double[]) { + double[] v = (double[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('D', cw.newDouble(v[i]).index); + } + } else { + Item i = cw.newConstItem(value); + bv.put12(".s.IFJDCS".charAt(i.type), i.index); + } + } + + @Override + public void visitEnum(final String name, final String desc, + final String value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation(final String name, + final String desc) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag and type, and reserve space for values count + bv.put12('@', cw.newUTF8(desc)).putShort(0); + return new AnnotationWriter(cw, true, bv, bv, bv.length - 2); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag, and reserve space for array size + bv.put12('[', 0); + return new AnnotationWriter(cw, false, bv, bv, bv.length - 2); + } + + @Override + public void visitEnd() { + if (parent != null) { + byte[] data = parent.data; + data[offset] = (byte) (size >>> 8); + data[offset + 1] = (byte) size; + } + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this annotation writer list. + * + * @return the size of this annotation writer list. + */ + int getSize() { + int size = 0; + AnnotationWriter aw = this; + while (aw != null) { + size += aw.bv.length; + aw = aw.next; + } + return size; + } + + /** + * Puts the annotations of this annotation writer list into the given byte + * vector. + * + * @param out + * where the annotations must be put. + */ + void put(final ByteVector out) { + int n = 0; + int size = 2; + AnnotationWriter aw = this; + AnnotationWriter last = null; + while (aw != null) { + ++n; + size += aw.bv.length; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putInt(size); + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + + /** + * Puts the given annotation lists into the given byte vector. + * + * @param panns + * an array of annotation writer lists. + * @param off + * index of the first annotation to be written. + * @param out + * where the annotations must be put. + */ + static void put(final AnnotationWriter[] panns, final int off, + final ByteVector out) { + int size = 1 + 2 * (panns.length - off); + for (int i = off; i < panns.length; ++i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + out.putInt(size).putByte(panns.length - off); + for (int i = off; i < panns.length; ++i) { + AnnotationWriter aw = panns[i]; + AnnotationWriter last = null; + int n = 0; + while (aw != null) { + ++n; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + } + + /** + * Puts the given type reference and type path into the given bytevector. + * LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported. + * + * @param typeRef + * a reference to the annotated type. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param out + * where the type reference and type path must be put. + */ + static void putTarget(int typeRef, TypePath typePath, ByteVector out) { + switch (typeRef >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + out.putShort(typeRef >>> 16); + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + out.putByte(typeRef >>> 24); + break; + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + out.putInt(typeRef); + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + out.put12(typeRef >>> 24, (typeRef & 0xFFFF00) >> 8); + break; + } + if (typePath == null) { + out.putByte(0); + } else { + int length = typePath.b[typePath.offset] * 2 + 1; + out.putByteArray(typePath.b, typePath.offset, length); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Attribute.java b/spring-core/src/main/java/org/springframework/asm/Attribute.java new file mode 100644 index 00000000..598df115 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Attribute.java @@ -0,0 +1,255 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A non standard class, field, method or code attribute. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** + * The type of this attribute. + */ + public final String type; + + /** + * The raw value of this attribute, used only for unknown attributes. + */ + byte[] value; + + /** + * The next attribute in this attribute list. May be <tt>null</tt>. + */ + Attribute next; + + /** + * Constructs a new empty attribute. + * + * @param type + * the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns <tt>true</tt> if this type of attribute is unknown. The default + * implementation of this method always returns <tt>true</tt>. + * + * @return <tt>true</tt> if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns <tt>true</tt> if this type of attribute is a code attribute. + * + * @return <tt>true</tt> if this type of attribute is a code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or <tt>null</tt> if + * this attribute is not a code attribute that contains labels. + */ + protected Label[] getLabels() { + return null; + } + + /** + * Reads a {@link #type type} attribute. This method must return a + * <i>new</i> {@link Attribute} object, of type {@link #type type}, + * corresponding to the <tt>len</tt> bytes starting at the given offset, in + * the given class reader. + * + * @param cr + * the class that contains the attribute to be read. + * @param off + * index of the first byte of the attribute's content in + * {@link ClassReader#b cr.b}. The 6 attribute header bytes, + * containing the type and the length of the attribute, are not + * taken into account here. + * @param len + * the length of the attribute's content. + * @param buf + * buffer to be used to call {@link ClassReader#readUTF8 + * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass} + * or {@link ClassReader#readConst readConst}. + * @param codeOff + * index of the first byte of code's attribute content in + * {@link ClassReader#b cr.b}, or -1 if the attribute to be read + * is not a code attribute. The 6 attribute header bytes, + * containing the type and the length of the attribute, are not + * taken into account here. + * @param labels + * the labels of the method's code, or <tt>null</tt> if the + * attribute to be read is not a code attribute. + * @return a <i>new</i> {@link Attribute} object corresponding to the given + * bytes. + */ + protected Attribute read(final ClassReader cr, final int off, + final int len, final char[] buf, final int codeOff, + final Label[] labels) { + Attribute attr = new Attribute(type); + attr.value = new byte[len]; + System.arraycopy(cr.b, off, attr.value, 0, len); + return attr; + } + + /** + * Returns the byte array form of this attribute. + * + * @param cw + * the class to which this attribute must be added. This + * parameter can be used to add to the constant pool of this + * class the items that corresponds to this attribute. + * @param code + * the bytecode of the method corresponding to this code + * attribute, or <tt>null</tt> if this attribute is not a code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to this + * code attribute, or <tt>null</tt> if this attribute is not a + * code attribute. + * @param maxStack + * the maximum stack size of the method corresponding to this + * code attribute, or -1 if this attribute is not a code + * attribute. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to this code attribute, or -1 if this attribute + * is not a code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write(final ClassWriter cw, final byte[] code, + final int len, final int maxStack, final int maxLocals) { + ByteVector v = new ByteVector(); + v.data = value; + v.length = value.length; + return v; + } + + /** + * Returns the length of the attribute list that begins with this attribute. + * + * @return the length of the attribute list that begins with this attribute. + */ + final int getCount() { + int count = 0; + Attribute attr = this; + while (attr != null) { + count += 1; + attr = attr.next; + } + return count; + } + + /** + * Returns the size of all the attributes in this attribute list. + * + * @param cw + * the class writer to be used to convert the attributes into + * byte arrays, with the {@link #write write} method. + * @param code + * the bytecode of the method corresponding to these code + * attributes, or <tt>null</tt> if these attributes are not code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to + * these code attributes, or <tt>null</tt> if these attributes + * are not code attributes. + * @param maxStack + * the maximum stack size of the method corresponding to these + * code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these + * attributes are not code attributes. + * @return the size of all the attributes in this attribute list. This size + * includes the size of the attribute headers. + */ + final int getSize(final ClassWriter cw, final byte[] code, final int len, + final int maxStack, final int maxLocals) { + Attribute attr = this; + int size = 0; + while (attr != null) { + cw.newUTF8(attr.type); + size += attr.write(cw, code, len, maxStack, maxLocals).length + 6; + attr = attr.next; + } + return size; + } + + /** + * Writes all the attributes of this attribute list in the given byte + * vector. + * + * @param cw + * the class writer to be used to convert the attributes into + * byte arrays, with the {@link #write write} method. + * @param code + * the bytecode of the method corresponding to these code + * attributes, or <tt>null</tt> if these attributes are not code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to + * these code attributes, or <tt>null</tt> if these attributes + * are not code attributes. + * @param maxStack + * the maximum stack size of the method corresponding to these + * code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these + * attributes are not code attributes. + * @param out + * where the attributes must be written. + */ + final void put(final ClassWriter cw, final byte[] code, final int len, + final int maxStack, final int maxLocals, final ByteVector out) { + Attribute attr = this; + while (attr != null) { + ByteVector b = attr.write(cw, code, len, maxStack, maxLocals); + out.putShort(cw.newUTF8(attr.type)).putInt(b.length); + out.putByteArray(b.data, 0, b.length); + attr = attr.next; + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/ByteVector.java b/spring-core/src/main/java/org/springframework/asm/ByteVector.java new file mode 100644 index 00000000..4992f1c8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/ByteVector.java @@ -0,0 +1,339 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to + * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** + * The content of this vector. + */ + byte[] data; + + /** + * Actual number of bytes in this vector. + */ + int length; + + /** + * Constructs a new {@link ByteVector ByteVector} with a default initial + * size. + */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector ByteVector} with the given initial + * size. + * + * @param initialSize + * the initial size of the byte vector to be constructed. + */ + public ByteVector(final int initialSize) { + data = new byte[initialSize]; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b + * a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int b) { + int length = this.length; + if (length + 1 > data.length) { + enlarge(1); + } + data[length++] = (byte) b; + this.length = length; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b1 + * a byte. + * @param b2 + * another byte. + * @return this byte vector. + */ + ByteVector put11(final int b1, final int b2) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) b1; + data[length++] = (byte) b2; + this.length = length; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param s + * a short. + * @return this byte vector. + */ + public ByteVector putShort(final int s) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b + * a byte. + * @param s + * a short. + * @return this byte vector. + */ + ByteVector put12(final int b, final int s) { + int length = this.length; + if (length + 3 > data.length) { + enlarge(3); + } + byte[] data = this.data; + data[length++] = (byte) b; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param i + * an int. + * @return this byte vector. + */ + public ByteVector putInt(final int i) { + int length = this.length; + if (length + 4 > data.length) { + enlarge(4); + } + byte[] data = this.data; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param l + * a long. + * @return this byte vector. + */ + public ByteVector putLong(final long l) { + int length = this.length; + if (length + 8 > data.length) { + enlarge(8); + } + byte[] data = this.data; + int i = (int) (l >>> 32); + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + i = (int) l; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param s + * a String whose UTF8 encoded length must be less than 65536. + * @return this byte vector. + */ + public ByteVector putUTF8(final String s) { + int charLength = s.length(); + if (charLength > 65535) { + throw new IllegalArgumentException(); + } + int len = length; + if (len + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] data = this.data; + // optimistic algorithm: instead of computing the byte length and then + // serializing the string (which requires two loops), we assume the byte + // length is equal to char length (which is the most frequent case), and + // we start serializing the string right away. During the serialization, + // if we find that this assumption is wrong, we continue with the + // general method. + data[len++] = (byte) (charLength >>> 8); + data[len++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char c = s.charAt(i); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else { + length = len; + return encodeUTF8(s, i, 65535); + } + } + length = len; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. The string length is encoded in two + * bytes before the encoded characters, if there is space for that (i.e. if + * this.length - i - 2 >= 0). + * + * @param s + * the String to encode. + * @param i + * the index of the first character to encode. The previous + * characters are supposed to have already been encoded, using + * only one byte per character. + * @param maxByteLength + * the maximum byte length of the encoded string, including the + * already encoded characters. + * @return this byte vector. + */ + ByteVector encodeUTF8(final String s, int i, int maxByteLength) { + int charLength = s.length(); + int byteLength = i; + char c; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + byteLength++; + } else if (c > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + if (byteLength > maxByteLength) { + throw new IllegalArgumentException(); + } + int start = length - i - 2; + if (start >= 0) { + data[start] = (byte) (byteLength >>> 8); + data[start + 1] = (byte) byteLength; + } + if (length + byteLength - i > data.length) { + enlarge(byteLength - i); + } + int len = length; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else if (c > '\u07FF') { + data[len++] = (byte) (0xE0 | c >> 12 & 0xF); + data[len++] = (byte) (0x80 | c >> 6 & 0x3F); + data[len++] = (byte) (0x80 | c & 0x3F); + } else { + data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); + data[len++] = (byte) (0x80 | c & 0x3F); + } + } + length = len; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b + * an array of bytes. May be <tt>null</tt> to put <tt>len</tt> + * null bytes into this byte vector. + * @param off + * index of the fist byte of b that must be copied. + * @param len + * number of bytes of b that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray(final byte[] b, final int off, final int len) { + if (length + len > data.length) { + enlarge(len); + } + if (b != null) { + System.arraycopy(b, off, data, length, len); + } + length += len; + return this; + } + + /** + * Enlarge this byte vector so that it can receive n more bytes. + * + * @param size + * number of additional bytes that this byte vector should be + * able to receive. + */ + private void enlarge(final int size) { + int length1 = 2 * data.length; + int length2 = length + size; + byte[] newData = new byte[length1 > length2 ? length1 : length2]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java new file mode 100644 index 00000000..978977fa --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java @@ -0,0 +1,2511 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A Java class parser to make a {@link ClassVisitor} visit an existing class. + * This class parses a byte array conforming to the Java class file format and + * calls the appropriate visit methods of a given class visitor for each field, + * method and bytecode instruction encountered. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * True to enable signatures support. + */ + static final boolean SIGNATURES = true; + + /** + * True to enable annotations support. + */ + static final boolean ANNOTATIONS = true; + + /** + * True to enable stack map frames support. + */ + static final boolean FRAMES = true; + + /** + * True to enable bytecode writing support. + */ + static final boolean WRITER = true; + + /** + * True to enable JSR_W and GOTO_W support. + */ + static final boolean RESIZE = true; + + /** + * Flag to skip method code. If this class is set <code>CODE</code> + * attribute won't be visited. This can be used, for example, to retrieve + * annotations for methods and method parameters. + */ + public static final int SKIP_CODE = 1; + + /** + * Flag to skip the debug information in the class. If this flag is set the + * debug information of the class is not visited, i.e. the + * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be + * called. + */ + public static final int SKIP_DEBUG = 2; + + /** + * Flag to skip the stack map frames in the class. If this flag is set the + * stack map frames of the class is not visited, i.e. the + * {@link MethodVisitor#visitFrame visitFrame} method will not be called. + * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is + * used: it avoids visiting frames that will be ignored and recomputed from + * scratch in the class writer. + */ + public static final int SKIP_FRAMES = 4; + + /** + * Flag to expand the stack map frames. By default stack map frames are + * visited in their original format (i.e. "expanded" for classes whose + * version is less than V1_6, and "compressed" for the other classes). If + * this flag is set, stack map frames are always visited in expanded format + * (this option adds a decompression/recompression step in ClassReader and + * ClassWriter which degrades performances quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * The class to be parsed. <i>The content of this array must not be + * modified. This field is intended for {@link Attribute} sub classes, and + * is normally not needed by class generators or adapters.</i> + */ + public final byte[] b; + + /** + * The start index of each constant pool item in {@link #b b}, plus one. The + * one byte offset skips the constant pool item tag that indicates its type. + */ + private final int[] items; + + /** + * The String objects corresponding to the CONSTANT_Utf8 items. This cache + * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, + * which GREATLY improves performances (by a factor 2 to 3). This caching + * strategy could be extended to all constant pool items, but its benefit + * would not be so great for these items (because they are much less + * expensive to parse than CONSTANT_Utf8 items). + */ + private final String[] strings; + + /** + * Maximum length of the strings contained in the constant pool of the + * class. + */ + private final int maxStringLength; + + /** + * Start index of the class header information (access, name...) in + * {@link #b b}. + */ + public final int header; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b + * the bytecode of the class to be read. + */ + public ClassReader(final byte[] b) { + this(b, 0, b.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b + * the bytecode of the class to be read. + * @param off + * the start offset of the class data. + * @param len + * the length of the class data. + */ + public ClassReader(final byte[] b, final int off, final int len) { + this.b = b; + // checks the class version + /* SPRING PATCH: REMOVED FOR FORWARD COMPATIBILITY WITH JDK 9 + if (readShort(off + 6) > Opcodes.V1_8) { + throw new IllegalArgumentException(); + } + */ + // parses the constant pool + items = new int[readUnsignedShort(off + 8)]; + int n = items.length; + strings = new String[n]; + int max = 0; + int index = off + 10; + for (int i = 1; i < n; ++i) { + items[i] = index + 1; + int size; + switch (b[index]) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + case ClassWriter.INT: + case ClassWriter.FLOAT: + case ClassWriter.NAME_TYPE: + case ClassWriter.INDY: + size = 5; + break; + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + size = 9; + ++i; + break; + case ClassWriter.UTF8: + size = 3 + readUnsignedShort(index + 1); + if (size > max) { + max = size; + } + break; + case ClassWriter.HANDLE: + size = 4; + break; + // case ClassWriter.CLASS: + // case ClassWriter.STR: + // case ClassWriter.MTYPE + default: + size = 3; + break; + } + index += size; + } + maxStringLength = max; + // the class header information starts just after the constant pool + header = index; + } + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may + * not reflect Deprecated and Synthetic flags when bytecode is before 1.5 + * and those flags are represented by attributes. + * + * @return the class access flags + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the internal class name + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, the + * super class is {@link Object}. + * + * @return the internal name of super class, or <tt>null</tt> for + * {@link Object} class. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the array of internal names for all implemented interfaces or + * <tt>null</tt>. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + int index = header + 6; + int n = readUnsignedShort(index); + String[] interfaces = new String[n]; + if (n > 0) { + char[] buf = new char[maxStringLength]; + for (int i = 0; i < n; ++i) { + index += 2; + interfaces[i] = readClass(index, buf); + } + } + return interfaces; + } + + /** + * Copies the constant pool data into the given {@link ClassWriter}. Should + * be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter + * the {@link ClassWriter} to copy constant pool into. + */ + void copyPool(final ClassWriter classWriter) { + char[] buf = new char[maxStringLength]; + int ll = items.length; + Item[] items2 = new Item[ll]; + for (int i = 1; i < ll; i++) { + int index = items[i]; + int tag = b[index - 1]; + Item item = new Item(i); + int nameType; + switch (tag) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + nameType = items[readUnsignedShort(index + 2)]; + item.set(tag, readClass(index, buf), readUTF8(nameType, buf), + readUTF8(nameType + 2, buf)); + break; + case ClassWriter.INT: + item.set(readInt(index)); + break; + case ClassWriter.FLOAT: + item.set(Float.intBitsToFloat(readInt(index))); + break; + case ClassWriter.NAME_TYPE: + item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf), + null); + break; + case ClassWriter.LONG: + item.set(readLong(index)); + ++i; + break; + case ClassWriter.DOUBLE: + item.set(Double.longBitsToDouble(readLong(index))); + ++i; + break; + case ClassWriter.UTF8: { + String s = strings[i]; + if (s == null) { + index = items[i]; + s = strings[i] = readUTF(index + 2, + readUnsignedShort(index), buf); + } + item.set(tag, s, null, null); + break; + } + case ClassWriter.HANDLE: { + int fieldOrMethodRef = items[readUnsignedShort(index + 1)]; + nameType = items[readUnsignedShort(fieldOrMethodRef + 2)]; + item.set(ClassWriter.HANDLE_BASE + readByte(index), + readClass(fieldOrMethodRef, buf), + readUTF8(nameType, buf), readUTF8(nameType + 2, buf)); + break; + } + case ClassWriter.INDY: + if (classWriter.bootstrapMethods == null) { + copyBootstrapMethods(classWriter, items2, buf); + } + nameType = items[readUnsignedShort(index + 2)]; + item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf), + readUnsignedShort(index)); + break; + // case ClassWriter.STR: + // case ClassWriter.CLASS: + // case ClassWriter.MTYPE + default: + item.set(tag, readUTF8(index, buf), null, null); + break; + } + + int index2 = item.hashCode % items2.length; + item.next = items2[index2]; + items2[index2] = item; + } + + int off = items[1] - 1; + classWriter.pool.putByteArray(b, off, header - off); + classWriter.items = items2; + classWriter.threshold = (int) (0.75d * ll); + classWriter.index = ll; + } + + /** + * Copies the bootstrap method data into the given {@link ClassWriter}. + * Should be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter + * the {@link ClassWriter} to copy bootstrap methods into. + */ + private void copyBootstrapMethods(final ClassWriter classWriter, + final Item[] items, final char[] c) { + // finds the "BootstrapMethods" attribute + int u = getAttributes(); + boolean found = false; + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + if ("BootstrapMethods".equals(attrName)) { + found = true; + break; + } + u += 6 + readInt(u + 4); + } + if (!found) { + return; + } + // copies the bootstrap methods in the class writer + int boostrapMethodCount = readUnsignedShort(u + 8); + for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) { + int position = v - u - 10; + int hashCode = readConst(readUnsignedShort(v), c).hashCode(); + for (int k = readUnsignedShort(v + 2); k > 0; --k) { + hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode(); + v += 2; + } + v += 4; + Item item = new Item(j); + item.set(position, hashCode & 0x7FFFFFFF); + int index = item.hashCode % items.length; + item.next = items[index]; + items[index] = item; + } + int attrSize = readInt(u + 4); + ByteVector bootstrapMethods = new ByteVector(attrSize + 62); + bootstrapMethods.putByteArray(b, u + 10, attrSize - 2); + classWriter.bootstrapMethodsCount = boostrapMethodCount; + classWriter.bootstrapMethods = bootstrapMethods; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param is + * an input stream from which to read the class. + * @throws IOException + * if a problem occurs during reading. + */ + public ClassReader(final InputStream is) throws IOException { + this(readClass(is, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param name + * the binary qualified name of the class to be read. + * @throws IOException + * if an exception occurs during reading. + */ + public ClassReader(final String name) throws IOException { + this(readClass( + ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + + ".class"), true)); + } + + /** + * Reads the bytecode of a class. + * + * @param is + * an input stream from which to read the class. + * @param close + * true to close the input stream after reading. + * @return the bytecode read from the given input stream. + * @throws IOException + * if a problem occurs during reading. + */ + private static byte[] readClass(final InputStream is, boolean close) + throws IOException { + if (is == null) { + throw new IOException("Class not found"); + } + try { + byte[] b = new byte[is.available()]; + int len = 0; + while (true) { + int n = is.read(b, len, b.length - len); + if (n == -1) { + if (len < b.length) { + byte[] c = new byte[len]; + System.arraycopy(b, 0, c, 0, len); + b = c; + } + return b; + } + len += n; + if (len == b.length) { + int last = is.read(); + if (last < 0) { + return b; + } + byte[] c = new byte[b.length + 1000]; + System.arraycopy(b, 0, c, 0, len); + c[len++] = (byte) last; + b = c; + } + } + } finally { + if (close) { + is.close(); + } + } + } + + // ------------------------------------------------------------------------ + // Public methods + // ------------------------------------------------------------------------ + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader} + * . This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor + * the visitor that must visit this class. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} + * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, final int flags) { + accept(classVisitor, new Attribute[0], flags); + } + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor + * the visitor that must visit this class. + * @param attrs + * prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to + * the type of one the prototypes will not be parsed: its byte + * array value will be passed unchanged to the ClassWriter. + * <i>This may corrupt it if this value contains references to + * the constant pool, or has syntactic or semantic links with a + * class element that has been transformed by a class adapter + * between the reader and the writer</i>. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} + * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, + final Attribute[] attrs, final int flags) { + int u = header; // current offset in the class file + char[] c = new char[maxStringLength]; // buffer used to read strings + + Context context = new Context(); + context.attrs = attrs; + context.flags = flags; + context.buffer = c; + + // reads the class declaration + int access = readUnsignedShort(u); + String name = readClass(u + 2, c); + String superClass = readClass(u + 4, c); + String[] interfaces = new String[readUnsignedShort(u + 6)]; + u += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(u, c); + u += 2; + } + + // reads the class attributes + String signature = null; + String sourceFile = null; + String sourceDebug = null; + String enclosingOwner = null; + String enclosingName = null; + String enclosingDesc = null; + int anns = 0; + int ianns = 0; + int tanns = 0; + int itanns = 0; + int innerClasses = 0; + Attribute attributes = null; + + u = getAttributes(); + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("SourceFile".equals(attrName)) { + sourceFile = readUTF8(u + 8, c); + } else if ("InnerClasses".equals(attrName)) { + innerClasses = u + 8; + } else if ("EnclosingMethod".equals(attrName)) { + enclosingOwner = readClass(u + 8, c); + int item = readUnsignedShort(u + 10); + if (item != 0) { + enclosingName = readUTF8(items[item], c); + enclosingDesc = readUTF8(items[item] + 2, c); + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if ("SourceDebugExtension".equals(attrName)) { + int len = readInt(u + 4); + sourceDebug = readUTF(u + 8, len, new char[len]); + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; + } else if ("BootstrapMethods".equals(attrName)) { + int[] bootstrapMethods = new int[readUnsignedShort(u + 8)]; + for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) { + bootstrapMethods[j] = v; + v += 2 + readUnsignedShort(v + 2) << 1; + } + context.bootstrapMethods = bootstrapMethods; + } else { + Attribute attr = readAttribute(attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + + // visits the class declaration + classVisitor.visit(readInt(items[1] - 7), access, name, signature, + superClass, interfaces); + + // visits the source and debug info + if ((flags & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebug != null)) { + classVisitor.visitSource(sourceFile, sourceDebug); + } + + // visits the outer class + if (enclosingOwner != null) { + classVisitor.visitOuterClass(enclosingOwner, enclosingName, + enclosingDesc); + } + + // visits the class annotations and type annotations + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } + + // visits the attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + classVisitor.visitAttribute(attributes); + attributes = attr; + } + + // visits the inner classes + if (innerClasses != 0) { + int v = innerClasses + 2; + for (int i = readUnsignedShort(innerClasses); i > 0; --i) { + classVisitor.visitInnerClass(readClass(v, c), + readClass(v + 2, c), readUTF8(v + 4, c), + readUnsignedShort(v + 6)); + v += 8; + } + } + + // visits the fields and methods + u = header + 10 + 2 * interfaces.length; + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + u = readField(classVisitor, context, u); + } + u += 2; + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + u = readMethod(classVisitor, context, u); + } + + // visits the end of the class + classVisitor.visitEnd(); + } + + /** + * Reads a field and makes the given visitor visit it. + * + * @param classVisitor + * the visitor that must visit the field. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the field in the class file. + * @return the offset of the first byte following the field in the class. + */ + private int readField(final ClassVisitor classVisitor, + final Context context, int u) { + // reads the field declaration + char[] c = context.buffer; + int access = readUnsignedShort(u); + String name = readUTF8(u + 2, c); + String desc = readUTF8(u + 4, c); + u += 6; + + // reads the field attributes + String signature = null; + int anns = 0; + int ianns = 0; + int tanns = 0; + int itanns = 0; + Object value = null; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("ConstantValue".equals(attrName)) { + int item = readUnsignedShort(u + 8); + value = item == 0 ? null : readConst(item, c); + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; + } else { + Attribute attr = readAttribute(context.attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // visits the field declaration + FieldVisitor fv = classVisitor.visitField(access, name, desc, + signature, value); + if (fv == null) { + return u; + } + + // visits the field annotations and type annotations + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + fv.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + fv.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + fv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + fv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } + + // visits the field attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + fv.visitAttribute(attributes); + attributes = attr; + } + + // visits the end of the field + fv.visitEnd(); + + return u; + } + + /** + * Reads a method and makes the given visitor visit it. + * + * @param classVisitor + * the visitor that must visit the method. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the method in the class file. + * @return the offset of the first byte following the method in the class. + */ + private int readMethod(final ClassVisitor classVisitor, + final Context context, int u) { + // reads the method declaration + char[] c = context.buffer; + context.access = readUnsignedShort(u); + context.name = readUTF8(u + 2, c); + context.desc = readUTF8(u + 4, c); + u += 6; + + // reads the method attributes + int code = 0; + int exception = 0; + String[] exceptions = null; + String signature = null; + int methodParameters = 0; + int anns = 0; + int ianns = 0; + int tanns = 0; + int itanns = 0; + int dann = 0; + int mpanns = 0; + int impanns = 0; + int firstAttribute = u; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("Code".equals(attrName)) { + if ((context.flags & SKIP_CODE) == 0) { + code = u + 8; + } + } else if ("Exceptions".equals(attrName)) { + exceptions = new String[readUnsignedShort(u + 8)]; + exception = u + 10; + for (int j = 0; j < exceptions.length; ++j) { + exceptions[j] = readClass(exception, c); + exception += 2; + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if ("Deprecated".equals(attrName)) { + context.access |= Opcodes.ACC_DEPRECATED; + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; + } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { + dann = u + 8; + } else if ("Synthetic".equals(attrName)) { + context.access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleParameterAnnotations".equals(attrName)) { + mpanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleParameterAnnotations".equals(attrName)) { + impanns = u + 8; + } else if ("MethodParameters".equals(attrName)) { + methodParameters = u + 8; + } else { + Attribute attr = readAttribute(context.attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // visits the method declaration + MethodVisitor mv = classVisitor.visitMethod(context.access, + context.name, context.desc, signature, exceptions); + if (mv == null) { + return u; + } + + /* + * if the returned MethodVisitor is in fact a MethodWriter, it means + * there is no method adapter between the reader and the writer. If, in + * addition, the writer's constant pool was copied from this reader + * (mw.cw.cr == this), and the signature and exceptions of the method + * have not been changed, then it is possible to skip all visit events + * and just copy the original code of the method to the writer (the + * access, name and descriptor can have been changed, this is not + * important since they are not copied as is from the reader). + */ + if (WRITER && mv instanceof MethodWriter) { + MethodWriter mw = (MethodWriter) mv; + if (mw.cw.cr == this && signature == mw.signature) { + boolean sameExceptions = false; + if (exceptions == null) { + sameExceptions = mw.exceptionCount == 0; + } else if (exceptions.length == mw.exceptionCount) { + sameExceptions = true; + for (int j = exceptions.length - 1; j >= 0; --j) { + exception -= 2; + if (mw.exceptions[j] != readUnsignedShort(exception)) { + sameExceptions = false; + break; + } + } + } + if (sameExceptions) { + /* + * we do not copy directly the code into MethodWriter to + * save a byte array copy operation. The real copy will be + * done in ClassWriter.toByteArray(). + */ + mw.classReaderOffset = firstAttribute; + mw.classReaderLength = u - firstAttribute; + return u; + } + } + } + + // visit the method parameters + if (methodParameters != 0) { + for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) { + mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2)); + } + } + + // visits the method annotations + if (ANNOTATIONS && dann != 0) { + AnnotationVisitor dv = mv.visitAnnotationDefault(); + readAnnotationValue(dann, c, null, dv); + if (dv != null) { + dv.visitEnd(); + } + } + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + mv.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + mv.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + mv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + mv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && mpanns != 0) { + readParameterAnnotations(mv, context, mpanns, true); + } + if (ANNOTATIONS && impanns != 0) { + readParameterAnnotations(mv, context, impanns, false); + } + + // visits the method attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + mv.visitAttribute(attributes); + attributes = attr; + } + + // visits the method code + if (code != 0) { + mv.visitCode(); + readCode(mv, context, code); + } + + // visits the end of the method + mv.visitEnd(); + + return u; + } + + /** + * Reads the bytecode of a method and makes the given visitor visit it. + * + * @param mv + * the visitor that must visit the method's code. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the code attribute in the class file. + */ + private void readCode(final MethodVisitor mv, final Context context, int u) { + // reads the header + byte[] b = this.b; + char[] c = context.buffer; + int maxStack = readUnsignedShort(u); + int maxLocals = readUnsignedShort(u + 2); + int codeLength = readInt(u + 4); + u += 8; + + // reads the bytecode to find the labels + int codeStart = u; + int codeEnd = u + codeLength; + Label[] labels = context.labels = new Label[codeLength + 2]; + readLabel(codeLength + 1, labels); + while (u < codeEnd) { + int offset = u - codeStart; + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + readLabel(offset + readShort(u + 1), labels); + u += 3; + break; + case ClassWriter.LABELW_INSN: + readLabel(offset + readInt(u + 1), labels); + u += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + readLabel(offset + readInt(u), labels); + for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) { + readLabel(offset + readInt(u + 12), labels); + u += 4; + } + u += 12; + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + readLabel(offset + readInt(u), labels); + for (int i = readInt(u + 4); i > 0; --i) { + readLabel(offset + readInt(u + 12), labels); + u += 8; + } + u += 8; + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case MANA_INSN: + default: + u += 4; + break; + } + } + + // reads the try catch entries to find the labels, and also visits them + for (int i = readUnsignedShort(u); i > 0; --i) { + Label start = readLabel(readUnsignedShort(u + 2), labels); + Label end = readLabel(readUnsignedShort(u + 4), labels); + Label handler = readLabel(readUnsignedShort(u + 6), labels); + String type = readUTF8(items[readUnsignedShort(u + 8)], c); + mv.visitTryCatchBlock(start, end, handler, type); + u += 8; + } + u += 2; + + // reads the code attributes + int[] tanns = null; // start index of each visible type annotation + int[] itanns = null; // start index of each invisible type annotation + int tann = 0; // current index in tanns array + int itann = 0; // current index in itanns array + int ntoff = -1; // next visible type annotation code offset + int nitoff = -1; // next invisible type annotation code offset + int varTable = 0; + int varTypeTable = 0; + boolean zip = true; + boolean unzip = (context.flags & EXPAND_FRAMES) != 0; + int stackMap = 0; + int stackMapSize = 0; + int frameCount = 0; + Context frame = null; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + if ("LocalVariableTable".equals(attrName)) { + if ((context.flags & SKIP_DEBUG) == 0) { + varTable = u + 8; + for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { + int label = readUnsignedShort(v + 10); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + label += readUnsignedShort(v + 12); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + v += 10; + } + } + } else if ("LocalVariableTypeTable".equals(attrName)) { + varTypeTable = u + 8; + } else if ("LineNumberTable".equals(attrName)) { + if ((context.flags & SKIP_DEBUG) == 0) { + for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { + int label = readUnsignedShort(v + 10); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + Label l = labels[label]; + while (l.line > 0) { + if (l.next == null) { + l.next = new Label(); + } + l = l.next; + } + l.line = readUnsignedShort(v + 12); + v += 4; + } + } + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = readTypeAnnotations(mv, context, u + 8, true); + ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1 + : readUnsignedShort(tanns[0] + 1); + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = readTypeAnnotations(mv, context, u + 8, false); + nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1 + : readUnsignedShort(itanns[0] + 1); + } else if (FRAMES && "StackMapTable".equals(attrName)) { + if ((context.flags & SKIP_FRAMES) == 0) { + stackMap = u + 10; + stackMapSize = readInt(u + 4); + frameCount = readUnsignedShort(u + 8); + } + /* + * here we do not extract the labels corresponding to the + * attribute content. This would require a full parsing of the + * attribute, which would need to be repeated in the second + * phase (see below). Instead the content of the attribute is + * read one frame at a time (i.e. after a frame has been + * visited, the next frame is read), and the labels it contains + * are also extracted one frame at a time. Thanks to the + * ordering of frames, having only a "one frame lookahead" is + * not a problem, i.e. it is not possible to see an offset + * smaller than the offset of the current insn and for which no + * Label exist. + */ + /* + * This is not true for UNINITIALIZED type offsets. We solve + * this by parsing the stack map table without a full decoding + * (see below). + */ + } else if (FRAMES && "StackMap".equals(attrName)) { + if ((context.flags & SKIP_FRAMES) == 0) { + zip = false; + stackMap = u + 10; + stackMapSize = readInt(u + 4); + frameCount = readUnsignedShort(u + 8); + } + /* + * IMPORTANT! here we assume that the frames are ordered, as in + * the StackMapTable attribute, although this is not guaranteed + * by the attribute format. + */ + } else { + for (int j = 0; j < context.attrs.length; ++j) { + if (context.attrs[j].type.equals(attrName)) { + Attribute attr = context.attrs[j].read(this, u + 8, + readInt(u + 4), c, codeStart - 8, labels); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // generates the first (implicit) stack map frame + if (FRAMES && stackMap != 0) { + /* + * for the first explicit frame the offset is not offset_delta + 1 + * but only offset_delta; setting the implicit frame offset to -1 + * allow the use of the "offset_delta + 1" rule in all cases + */ + frame = context; + frame.offset = -1; + frame.mode = 0; + frame.localCount = 0; + frame.localDiff = 0; + frame.stackCount = 0; + frame.local = new Object[maxLocals]; + frame.stack = new Object[maxStack]; + if (unzip) { + getImplicitFrame(context); + } + /* + * Finds labels for UNINITIALIZED frame types. Instead of decoding + * each element of the stack map table, we look for 3 consecutive + * bytes that "look like" an UNINITIALIZED type (tag 8, offset + * within code bounds, NEW instruction at this offset). We may find + * false positives (i.e. not real UNINITIALIZED types), but this + * should be rare, and the only consequence will be the creation of + * an unneeded label. This is better than creating a label for each + * NEW instruction, and faster than fully decoding the whole stack + * map table. + */ + for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) { + if (b[i] == 8) { // UNINITIALIZED FRAME TYPE + int v = readUnsignedShort(i + 1); + if (v >= 0 && v < codeLength) { + if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) { + readLabel(v, labels); + } + } + } + } + } + + // visits the instructions + u = codeStart; + while (u < codeEnd) { + int offset = u - codeStart; + + // visits the label and line number for this offset, if any + Label l = labels[offset]; + if (l != null) { + Label next = l.next; + l.next = null; + mv.visitLabel(l); + if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) { + mv.visitLineNumber(l.line, l); + while (next != null) { + mv.visitLineNumber(next.line, l); + next = next.next; + } + } + } + + // visits the frame for this offset, if any + while (FRAMES && frame != null + && (frame.offset == offset || frame.offset == -1)) { + // if there is a frame for this offset, makes the visitor visit + // it, and reads the next frame if there is one. + if (frame.offset != -1) { + if (!zip || unzip) { + mv.visitFrame(Opcodes.F_NEW, frame.localCount, + frame.local, frame.stackCount, frame.stack); + } else { + mv.visitFrame(frame.mode, frame.localDiff, frame.local, + frame.stackCount, frame.stack); + } + } + if (frameCount > 0) { + stackMap = readFrame(stackMap, zip, unzip, frame); + --frameCount; + } else { + frame = null; + } + } + + // visits the instruction at this offset + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + mv.visitInsn(opcode); + u += 1; + break; + case ClassWriter.IMPLVAR_INSN: + if (opcode > Opcodes.ISTORE) { + opcode -= 59; // ISTORE_0 + mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), + opcode & 0x3); + } else { + opcode -= 26; // ILOAD_0 + mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + } + u += 1; + break; + case ClassWriter.LABEL_INSN: + mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]); + u += 3; + break; + case ClassWriter.LABELW_INSN: + mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); + u += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4)); + u += 6; + } else { + mv.visitVarInsn(opcode, readUnsignedShort(u + 2)); + u += 4; + } + break; + case ClassWriter.TABL_INSN: { + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + int label = offset + readInt(u); + int min = readInt(u + 4); + int max = readInt(u + 8); + Label[] table = new Label[max - min + 1]; + u += 12; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[offset + readInt(u)]; + u += 4; + } + mv.visitTableSwitchInsn(min, max, labels[label], table); + break; + } + case ClassWriter.LOOK_INSN: { + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + int label = offset + readInt(u); + int len = readInt(u + 4); + int[] keys = new int[len]; + Label[] values = new Label[len]; + u += 8; + for (int i = 0; i < len; ++i) { + keys[i] = readInt(u); + values[i] = labels[offset + readInt(u + 4)]; + u += 8; + } + mv.visitLookupSwitchInsn(labels[label], keys, values); + break; + } + case ClassWriter.VAR_INSN: + mv.visitVarInsn(opcode, b[u + 1] & 0xFF); + u += 2; + break; + case ClassWriter.SBYTE_INSN: + mv.visitIntInsn(opcode, b[u + 1]); + u += 2; + break; + case ClassWriter.SHORT_INSN: + mv.visitIntInsn(opcode, readShort(u + 1)); + u += 3; + break; + case ClassWriter.LDC_INSN: + mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c)); + u += 2; + break; + case ClassWriter.LDCW_INSN: + mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c)); + u += 3; + break; + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.ITFMETH_INSN: { + int cpIndex = items[readUnsignedShort(u + 1)]; + boolean itf = b[cpIndex - 1] == ClassWriter.IMETH; + String iowner = readClass(cpIndex, c); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + if (opcode < Opcodes.INVOKEVIRTUAL) { + mv.visitFieldInsn(opcode, iowner, iname, idesc); + } else { + mv.visitMethodInsn(opcode, iowner, iname, idesc, itf); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + u += 5; + } else { + u += 3; + } + break; + } + case ClassWriter.INDYMETH_INSN: { + int cpIndex = items[readUnsignedShort(u + 1)]; + int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)]; + Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c); + int bsmArgCount = readUnsignedShort(bsmIndex + 2); + Object[] bsmArgs = new Object[bsmArgCount]; + bsmIndex += 4; + for (int i = 0; i < bsmArgCount; i++) { + bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c); + bsmIndex += 2; + } + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs); + u += 5; + break; + } + case ClassWriter.TYPE_INSN: + mv.visitTypeInsn(opcode, readClass(u + 1, c)); + u += 3; + break; + case ClassWriter.IINC_INSN: + mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]); + u += 3; + break; + // case MANA_INSN: + default: + mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF); + u += 4; + break; + } + + // visit the instruction annotations, if any + while (tanns != null && tann < tanns.length && ntoff <= offset) { + if (ntoff == offset) { + int v = readAnnotationTarget(context, tanns[tann]); + readAnnotationValues(v + 2, c, true, + mv.visitInsnAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1 + : readUnsignedShort(tanns[tann] + 1); + } + while (itanns != null && itann < itanns.length && nitoff <= offset) { + if (nitoff == offset) { + int v = readAnnotationTarget(context, itanns[itann]); + readAnnotationValues(v + 2, c, true, + mv.visitInsnAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + nitoff = ++itann >= itanns.length + || readByte(itanns[itann]) < 0x43 ? -1 + : readUnsignedShort(itanns[itann] + 1); + } + } + if (labels[codeLength] != null) { + mv.visitLabel(labels[codeLength]); + } + + // visits the local variable tables + if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) { + int[] typeTable = null; + if (varTypeTable != 0) { + u = varTypeTable + 2; + typeTable = new int[readUnsignedShort(varTypeTable) * 3]; + for (int i = typeTable.length; i > 0;) { + typeTable[--i] = u + 6; // signature + typeTable[--i] = readUnsignedShort(u + 8); // index + typeTable[--i] = readUnsignedShort(u); // start + u += 10; + } + } + u = varTable + 2; + for (int i = readUnsignedShort(varTable); i > 0; --i) { + int start = readUnsignedShort(u); + int length = readUnsignedShort(u + 2); + int index = readUnsignedShort(u + 8); + String vsignature = null; + if (typeTable != null) { + for (int j = 0; j < typeTable.length; j += 3) { + if (typeTable[j] == start && typeTable[j + 1] == index) { + vsignature = readUTF8(typeTable[j + 2], c); + break; + } + } + } + mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c), + vsignature, labels[start], labels[start + length], + index); + u += 10; + } + } + + // visits the local variables type annotations + if (tanns != null) { + for (int i = 0; i < tanns.length; ++i) { + if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) { + int v = readAnnotationTarget(context, tanns[i]); + v = readAnnotationValues(v + 2, c, true, + mv.visitLocalVariableAnnotation(context.typeRef, + context.typePath, context.start, + context.end, context.index, readUTF8(v, c), + true)); + } + } + } + if (itanns != null) { + for (int i = 0; i < itanns.length; ++i) { + if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) { + int v = readAnnotationTarget(context, itanns[i]); + v = readAnnotationValues(v + 2, c, true, + mv.visitLocalVariableAnnotation(context.typeRef, + context.typePath, context.start, + context.end, context.index, readUTF8(v, c), + false)); + } + } + } + + // visits the code attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + mv.visitAttribute(attributes); + attributes = attr; + } + + // visits the max stack and max locals values + mv.visitMaxs(maxStack, maxLocals); + } + + /** + * Parses a type annotation table to find the labels, and to visit the try + * catch block annotations. + * + * @param u + * the start offset of a type annotation table. + * @param mv + * the method visitor to be used to visit the try catch block + * annotations. + * @param context + * information about the class being parsed. + * @param visible + * if the type annotation table to parse contains runtime visible + * annotations. + * @return the start offset of each type annotation in the parsed table. + */ + private int[] readTypeAnnotations(final MethodVisitor mv, + final Context context, int u, boolean visible) { + char[] c = context.buffer; + int[] offsets = new int[readUnsignedShort(u)]; + u += 2; + for (int i = 0; i < offsets.length; ++i) { + offsets[i] = u; + int target = readInt(u); + switch (target >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + u += 2; + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + u += 1; + break; + case 0x40: // LOCAL_VARIABLE + case 0x41: // RESOURCE_VARIABLE + for (int j = readUnsignedShort(u + 1); j > 0; --j) { + int start = readUnsignedShort(u + 3); + int length = readUnsignedShort(u + 5); + readLabel(start, context.labels); + readLabel(start + length, context.labels); + u += 6; + } + u += 3; + break; + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + u += 4; + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + u += 3; + break; + } + int pathLength = readByte(u); + if ((target >>> 24) == 0x42) { + TypePath path = pathLength == 0 ? null : new TypePath(b, u); + u += 1 + 2 * pathLength; + u = readAnnotationValues(u + 2, c, true, + mv.visitTryCatchAnnotation(target, path, + readUTF8(u, c), visible)); + } else { + u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null); + } + } + return offsets; + } + + /** + * Parses the header of a type annotation to extract its target_type and + * target_path (the result is stored in the given context), and returns the + * start offset of the rest of the type_annotation structure (i.e. the + * offset to the type_index field, which is followed by + * num_element_value_pairs and then the name,value pairs). + * + * @param context + * information about the class being parsed. This is where the + * extracted target_type and target_path must be stored. + * @param u + * the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readAnnotationTarget(final Context context, int u) { + int target = readInt(u); + switch (target >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + target &= 0xFFFF0000; + u += 2; + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + target &= 0xFF000000; + u += 1; + break; + case 0x40: // LOCAL_VARIABLE + case 0x41: { // RESOURCE_VARIABLE + target &= 0xFF000000; + int n = readUnsignedShort(u + 1); + context.start = new Label[n]; + context.end = new Label[n]; + context.index = new int[n]; + u += 3; + for (int i = 0; i < n; ++i) { + int start = readUnsignedShort(u); + int length = readUnsignedShort(u + 2); + context.start[i] = readLabel(start, context.labels); + context.end[i] = readLabel(start + length, context.labels); + context.index[i] = readUnsignedShort(u + 4); + u += 6; + } + break; + } + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + target &= 0xFF0000FF; + u += 4; + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000; + u += 3; + break; + } + int pathLength = readByte(u); + context.typeRef = target; + context.typePath = pathLength == 0 ? null : new TypePath(b, u); + return u + 1 + 2 * pathLength; + } + + /** + * Reads parameter annotations and makes the given visitor visit them. + * + * @param mv + * the visitor that must visit the annotations. + * @param context + * information about the class being parsed. + * @param v + * start offset in {@link #b b} of the annotations to be read. + * @param visible + * <tt>true</tt> if the annotations to be read are visible at + * runtime. + */ + private void readParameterAnnotations(final MethodVisitor mv, + final Context context, int v, final boolean visible) { + int i; + int n = b[v++] & 0xFF; + // workaround for a bug in javac (javac compiler generates a parameter + // annotation array whose size is equal to the number of parameters in + // the Java source file, while it should generate an array whose size is + // equal to the number of parameters in the method descriptor - which + // includes the synthetic parameters added by the compiler). This work- + // around supposes that the synthetic parameters are the first ones. + int synthetics = Type.getArgumentTypes(context.desc).length - n; + AnnotationVisitor av; + for (i = 0; i < synthetics; ++i) { + // virtual annotation to detect synthetic parameters in MethodWriter + av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false); + if (av != null) { + av.visitEnd(); + } + } + char[] c = context.buffer; + for (; i < n + synthetics; ++i) { + int j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible); + v = readAnnotationValues(v + 2, c, true, av); + } + } + } + + /** + * Reads the values of an annotation and makes the given visitor visit them. + * + * @param v + * the start offset in {@link #b b} of the values to be read + * (including the unsigned short that gives the number of + * values). + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param named + * if the annotation values are named or not. + * @param av + * the visitor that must visit the values. + * @return the end offset of the annotation values. + */ + private int readAnnotationValues(int v, final char[] buf, + final boolean named, final AnnotationVisitor av) { + int i = readUnsignedShort(v); + v += 2; + if (named) { + for (; i > 0; --i) { + v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av); + } + } else { + for (; i > 0; --i) { + v = readAnnotationValue(v, buf, null, av); + } + } + if (av != null) { + av.visitEnd(); + } + return v; + } + + /** + * Reads a value of an annotation and makes the given visitor visit it. + * + * @param v + * the start offset in {@link #b b} of the value to be read + * (<i>not including the value name constant pool index</i>). + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param name + * the name of the value to be read. + * @param av + * the visitor that must visit the value. + * @return the end offset of the annotation value. + */ + private int readAnnotationValue(int v, final char[] buf, final String name, + final AnnotationVisitor av) { + int i; + if (av == null) { + switch (b[v] & 0xFF) { + case 'e': // enum_const_value + return v + 5; + case '@': // annotation_value + return readAnnotationValues(v + 3, buf, true, null); + case '[': // array_value + return readAnnotationValues(v + 1, buf, false, null); + default: + return v + 3; + } + } + switch (b[v++] & 0xFF) { + case 'I': // pointer to CONSTANT_Integer + case 'J': // pointer to CONSTANT_Long + case 'F': // pointer to CONSTANT_Float + case 'D': // pointer to CONSTANT_Double + av.visit(name, readConst(readUnsignedShort(v), buf)); + v += 2; + break; + case 'B': // pointer to CONSTANT_Byte + av.visit(name, + new Byte((byte) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'Z': // pointer to CONSTANT_Boolean + av.visit(name, + readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE + : Boolean.TRUE); + v += 2; + break; + case 'S': // pointer to CONSTANT_Short + av.visit(name, new Short( + (short) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 'C': // pointer to CONSTANT_Char + av.visit(name, new Character( + (char) readInt(items[readUnsignedShort(v)]))); + v += 2; + break; + case 's': // pointer to CONSTANT_Utf8 + av.visit(name, readUTF8(v, buf)); + v += 2; + break; + case 'e': // enum_const_value + av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf)); + v += 4; + break; + case 'c': // class_info + av.visit(name, Type.getType(readUTF8(v, buf))); + v += 2; + break; + case '@': // annotation_value + v = readAnnotationValues(v + 2, buf, true, + av.visitAnnotation(name, readUTF8(v, buf))); + break; + case '[': // array_value + int size = readUnsignedShort(v); + v += 2; + if (size == 0) { + return readAnnotationValues(v - 2, buf, false, + av.visitArray(name)); + } + switch (this.b[v++] & 0xFF) { + case 'B': + byte[] bv = new byte[size]; + for (i = 0; i < size; i++) { + bv[i] = (byte) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, bv); + --v; + break; + case 'Z': + boolean[] zv = new boolean[size]; + for (i = 0; i < size; i++) { + zv[i] = readInt(items[readUnsignedShort(v)]) != 0; + v += 3; + } + av.visit(name, zv); + --v; + break; + case 'S': + short[] sv = new short[size]; + for (i = 0; i < size; i++) { + sv[i] = (short) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, sv); + --v; + break; + case 'C': + char[] cv = new char[size]; + for (i = 0; i < size; i++) { + cv[i] = (char) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, cv); + --v; + break; + case 'I': + int[] iv = new int[size]; + for (i = 0; i < size; i++) { + iv[i] = readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, iv); + --v; + break; + case 'J': + long[] lv = new long[size]; + for (i = 0; i < size; i++) { + lv[i] = readLong(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, lv); + --v; + break; + case 'F': + float[] fv = new float[size]; + for (i = 0; i < size; i++) { + fv[i] = Float + .intBitsToFloat(readInt(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, fv); + --v; + break; + case 'D': + double[] dv = new double[size]; + for (i = 0; i < size; i++) { + dv[i] = Double + .longBitsToDouble(readLong(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, dv); + --v; + break; + default: + v = readAnnotationValues(v - 3, buf, false, av.visitArray(name)); + } + } + return v; + } + + /** + * Computes the implicit frame of the method currently being parsed (as + * defined in the given {@link Context}) and stores it in the given context. + * + * @param frame + * information about the class being parsed. + */ + private void getImplicitFrame(final Context frame) { + String desc = frame.desc; + Object[] locals = frame.local; + int local = 0; + if ((frame.access & Opcodes.ACC_STATIC) == 0) { + if ("<init>".equals(frame.name)) { + locals[local++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[local++] = readClass(header + 2, frame.buffer); + } + } + int i = 1; + loop: while (true) { + int j = i; + switch (desc.charAt(i++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[local++] = Opcodes.INTEGER; + break; + case 'F': + locals[local++] = Opcodes.FLOAT; + break; + case 'J': + locals[local++] = Opcodes.LONG; + break; + case 'D': + locals[local++] = Opcodes.DOUBLE; + break; + case '[': + while (desc.charAt(i) == '[') { + ++i; + } + if (desc.charAt(i) == 'L') { + ++i; + while (desc.charAt(i) != ';') { + ++i; + } + } + locals[local++] = desc.substring(j, ++i); + break; + case 'L': + while (desc.charAt(i) != ';') { + ++i; + } + locals[local++] = desc.substring(j + 1, i++); + break; + default: + break loop; + } + } + frame.localCount = local; + } + + /** + * Reads a stack map frame and stores the result in the given + * {@link Context} object. + * + * @param stackMap + * the start offset of a stack map frame in the class file. + * @param zip + * if the stack map frame at stackMap is compressed or not. + * @param unzip + * if the stack map frame must be uncompressed. + * @param frame + * where the parsed stack map frame must be stored. + * @return the offset of the first byte following the parsed frame. + */ + private int readFrame(int stackMap, boolean zip, boolean unzip, + Context frame) { + char[] c = frame.buffer; + Label[] labels = frame.labels; + int tag; + int delta; + if (zip) { + tag = b[stackMap++] & 0xFF; + } else { + tag = MethodWriter.FULL_FRAME; + frame.offset = -1; + } + frame.localDiff = 0; + if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) { + delta = tag; + frame.mode = Opcodes.F_SAME; + frame.stackCount = 0; + } else if (tag < MethodWriter.RESERVED) { + delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; + stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); + frame.mode = Opcodes.F_SAME1; + frame.stackCount = 1; + } else { + delta = readUnsignedShort(stackMap); + stackMap += 2; + if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); + frame.mode = Opcodes.F_SAME1; + frame.stackCount = 1; + } else if (tag >= MethodWriter.CHOP_FRAME + && tag < MethodWriter.SAME_FRAME_EXTENDED) { + frame.mode = Opcodes.F_CHOP; + frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag; + frame.localCount -= frame.localDiff; + frame.stackCount = 0; + } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) { + frame.mode = Opcodes.F_SAME; + frame.stackCount = 0; + } else if (tag < MethodWriter.FULL_FRAME) { + int local = unzip ? frame.localCount : 0; + for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) { + stackMap = readFrameType(frame.local, local++, stackMap, c, + labels); + } + frame.mode = Opcodes.F_APPEND; + frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED; + frame.localCount += frame.localDiff; + frame.stackCount = 0; + } else { // if (tag == FULL_FRAME) { + frame.mode = Opcodes.F_FULL; + int n = readUnsignedShort(stackMap); + stackMap += 2; + frame.localDiff = n; + frame.localCount = n; + for (int local = 0; n > 0; n--) { + stackMap = readFrameType(frame.local, local++, stackMap, c, + labels); + } + n = readUnsignedShort(stackMap); + stackMap += 2; + frame.stackCount = n; + for (int stack = 0; n > 0; n--) { + stackMap = readFrameType(frame.stack, stack++, stackMap, c, + labels); + } + } + } + frame.offset += delta + 1; + readLabel(frame.offset, labels); + return stackMap; + } + + /** + * Reads a stack map frame type and stores it at the given index in the + * given array. + * + * @param frame + * the array where the parsed type must be stored. + * @param index + * the index in 'frame' where the parsed type must be stored. + * @param v + * the start offset of the stack map frame type to read. + * @param buf + * a buffer to read strings. + * @param labels + * the labels of the method currently being parsed, indexed by + * their offset. If the parsed type is an Uninitialized type, a + * new label for the corresponding NEW instruction is stored in + * this array if it does not already exist. + * @return the offset of the first byte after the parsed type. + */ + private int readFrameType(final Object[] frame, final int index, int v, + final char[] buf, final Label[] labels) { + int type = b[v++] & 0xFF; + switch (type) { + case 0: + frame[index] = Opcodes.TOP; + break; + case 1: + frame[index] = Opcodes.INTEGER; + break; + case 2: + frame[index] = Opcodes.FLOAT; + break; + case 3: + frame[index] = Opcodes.DOUBLE; + break; + case 4: + frame[index] = Opcodes.LONG; + break; + case 5: + frame[index] = Opcodes.NULL; + break; + case 6: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case 7: // Object + frame[index] = readClass(v, buf); + v += 2; + break; + default: // Uninitialized + frame[index] = readLabel(readUnsignedShort(v), labels); + v += 2; + } + return v; + } + + /** + * Returns the label corresponding to the given offset. The default + * implementation of this method creates a label for the given offset if it + * has not been already created. + * + * @param offset + * a bytecode offset in a method. + * @param labels + * the already created labels, indexed by their offset. If a + * label already exists for offset this method must not create a + * new one. Otherwise it must store the new label in this array. + * @return a non null Label, which must be equal to labels[offset]. + */ + protected Label readLabel(int offset, Label[] labels) { + if (labels[offset] == null) { + labels[offset] = new Label(); + } + return labels[offset]; + } + + /** + * Returns the start index of the attribute_info structure of this class. + * + * @return the start index of the attribute_info structure of this class. + */ + private int getAttributes() { + // skips the header + int u = header + 8 + readUnsignedShort(header + 6) * 2; + // skips fields and methods + for (int i = readUnsignedShort(u); i > 0; --i) { + for (int j = readUnsignedShort(u + 8); j > 0; --j) { + u += 6 + readInt(u + 12); + } + u += 8; + } + u += 2; + for (int i = readUnsignedShort(u); i > 0; --i) { + for (int j = readUnsignedShort(u + 8); j > 0; --j) { + u += 6 + readInt(u + 12); + } + u += 8; + } + // the attribute_info structure starts just after the methods + return u + 2; + } + + /** + * Reads an attribute in {@link #b b}. + * + * @param attrs + * prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to + * the type of one the prototypes is ignored (i.e. an empty + * {@link Attribute} instance is returned). + * @param type + * the type of the attribute. + * @param off + * index of the first byte of the attribute's content in + * {@link #b b}. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into + * account here (they have already been read). + * @param len + * the length of the attribute's content. + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param codeOff + * index of the first byte of code's attribute content in + * {@link #b b}, or -1 if the attribute to be read is not a code + * attribute. The 6 attribute header bytes, containing the type + * and the length of the attribute, are not taken into account + * here. + * @param labels + * the labels of the method's code, or <tt>null</tt> if the + * attribute to be read is not a code attribute. + * @return the attribute that has been read, or <tt>null</tt> to skip this + * attribute. + */ + private Attribute readAttribute(final Attribute[] attrs, final String type, + final int off, final int len, final char[] buf, final int codeOff, + final Label[] labels) { + for (int i = 0; i < attrs.length; ++i) { + if (attrs[i].type.equals(type)) { + return attrs[i].read(this, off, len, buf, codeOff, labels); + } + } + return new Attribute(type).read(this, off, len, null, -1, null); + } + + // ------------------------------------------------------------------------ + // Utility methods: low level parsing + // ------------------------------------------------------------------------ + + /** + * Returns the number of constant pool items in {@link #b b}. + * + * @return the number of constant pool items in {@link #b b}. + */ + public int getItemCount() { + return items.length; + } + + /** + * Returns the start index of the constant pool item in {@link #b b}, plus + * one. <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param item + * the index a constant pool item. + * @return the start index of the constant pool item in {@link #b b}, plus + * one. + */ + public int getItem(final int item) { + return items[item]; + } + + /** + * Returns the maximum length of the strings contained in the constant pool + * of the class. + * + * @return the maximum length of the strings contained in the constant pool + * of the class. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in {@link #b b}. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readByte(final int index) { + return b[index] & 0xFF; + } + + /** + * Reads an unsigned short value in {@link #b b}. <i>This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readUnsignedShort(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in {@link #b b}. <i>This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public short readShort(final int index) { + byte[] b = this.b; + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in {@link #b b}. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readInt(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Reads a signed long value in {@link #b b}. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public long readLong(final int index) { + long l1 = readInt(index); + long l0 = readInt(index + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method + * is intended for {@link Attribute} sub classes, and is normally not needed + * by class generators or adapters.</i> + * + * @param index + * the start index of an unsigned short value in {@link #b b}, + * whose value is the index of an UTF8 constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 item. + */ + public String readUTF8(int index, final char[] buf) { + int item = readUnsignedShort(index); + if (index == 0 || item == 0) { + return null; + } + String s = strings[item]; + if (s != null) { + return s; + } + index = items[item]; + return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); + } + + /** + * Reads UTF8 string in {@link #b b}. + * + * @param index + * start offset of the UTF8 string to be read. + * @param utfLen + * length of the UTF8 string to be read. + * @param buf + * buffer to be used to read the string. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(int index, final int utfLen, final char[] buf) { + int endIndex = index + utfLen; + byte[] b = this.b; + int strLen = 0; + int c; + int st = 0; + char cc = 0; + while (index < endIndex) { + c = b[index++]; + switch (st) { + case 0: + c = c & 0xFF; + if (c < 0x80) { // 0xxxxxxx + buf[strLen++] = (char) c; + } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx + cc = (char) (c & 0x1F); + st = 1; + } else { // 1110 xxxx 10xx xxxx 10xx xxxx + cc = (char) (c & 0x0F); + st = 2; + } + break; + + case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char + buf[strLen++] = (char) ((cc << 6) | (c & 0x3F)); + st = 0; + break; + + case 2: // byte 2 of 3-byte char + cc = (char) ((cc << 6) | (c & 0x3F)); + st = 1; + break; + } + } + return new String(buf, 0, strLen); + } + + /** + * Reads a class constant pool item in {@link #b b}. <i>This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters.</i> + * + * @param index + * the start index of an unsigned short value in {@link #b b}, + * whose value is the index of a class constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified class item. + */ + public String readClass(final int index, final char[] buf) { + // computes the start index of the CONSTANT_Class item in b + // and reads the CONSTANT_Utf8 item designated by + // the first two bytes of this CONSTANT_Class item + return readUTF8(items[readUnsignedShort(index)], buf); + } + + /** + * Reads a numeric or string constant pool item in {@link #b b}. <i>This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters.</i> + * + * @param item + * the index of a constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, + * {@link String}, {@link Type} or {@link Handle} corresponding to + * the given constant pool item. + */ + public Object readConst(final int item, final char[] buf) { + int index = items[item]; + switch (b[index - 1]) { + case ClassWriter.INT: + return new Integer(readInt(index)); + case ClassWriter.FLOAT: + return new Float(Float.intBitsToFloat(readInt(index))); + case ClassWriter.LONG: + return new Long(readLong(index)); + case ClassWriter.DOUBLE: + return new Double(Double.longBitsToDouble(readLong(index))); + case ClassWriter.CLASS: + return Type.getObjectType(readUTF8(index, buf)); + case ClassWriter.STR: + return readUTF8(index, buf); + case ClassWriter.MTYPE: + return Type.getMethodType(readUTF8(index, buf)); + default: // case ClassWriter.HANDLE_BASE + [1..9]: + int tag = readByte(index); + int[] items = this.items; + int cpIndex = items[readUnsignedShort(index + 1)]; + String owner = readClass(cpIndex, buf); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String name = readUTF8(cpIndex, buf); + String desc = readUTF8(cpIndex + 2, buf); + return new Handle(tag, owner, name, desc); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java new file mode 100644 index 00000000..ba58188a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java @@ -0,0 +1,322 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A visitor to visit a Java class. The methods of this class must be called in + * the following order: <tt>visit</tt> [ <tt>visitSource</tt> ] [ + * <tt>visitOuterClass</tt> ] ( <tt>visitAnnotation</tt> | + * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* ( + * <tt>visitInnerClass</tt> | <tt>visitField</tt> | <tt>visitMethod</tt> )* + * <tt>visitEnd</tt>. + * + * @author Eric Bruneton + */ +public abstract class ClassVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The class visitor to which this visitor must delegate method calls. May + * be null. + */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param cv + * the class visitor to which this visitor must delegate method + * calls. May be null. + */ + public ClassVisitor(final int api, final ClassVisitor cv) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.cv = cv; + } + + /** + * Visits the header of the class. + * + * @param version + * the class version. + * @param access + * the class's access flags (see {@link Opcodes}). This parameter + * also indicates if the class is deprecated. + * @param name + * the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * @param signature + * the signature of this class. May be <tt>null</tt> if the class + * is not a generic one, and does not extend or implement generic + * classes or interfaces. + * @param superName + * the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For + * interfaces, the super class is {@link Object}. May be + * <tt>null</tt>, but only for the {@link Object} class. + * @param interfaces + * the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + */ + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } + + /** + * Visits the source of the class. + * + * @param source + * the name of the source file from which the class was compiled. + * May be <tt>null</tt>. + * @param debug + * additional debug information to compute the correspondance + * between source and compiled elements of the class. May be + * <tt>null</tt>. + */ + public void visitSource(String source, String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } + + /** + * Visits the enclosing class of the class. This method must be called only + * if the class has an enclosing class. + * + * @param owner + * internal name of the enclosing class of the class. + * @param name + * the name of the method that contains the class, or + * <tt>null</tt> if the class is not enclosed in a method of its + * enclosing class. + * @param desc + * the descriptor of the method that contains the class, or + * <tt>null</tt> if the class is not enclosed in a method of its + * enclosing class. + */ + public void visitOuterClass(String owner, String name, String desc) { + if (cv != null) { + cv.visitOuterClass(owner, name, desc); + } + } + + /** + * Visits an annotation of the class. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (cv != null) { + return cv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the class signature. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#CLASS_TYPE_PARAMETER + * CLASS_TYPE_PARAMETER}, + * {@link TypeReference#CLASS_TYPE_PARAMETER_BOUND + * CLASS_TYPE_PARAMETER_BOUND} or + * {@link TypeReference#CLASS_EXTENDS CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (cv != null) { + return cv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the class. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (cv != null) { + cv.visitAttribute(attr); + } + } + + /** + * Visits information about an inner class. This inner class is not + * necessarily a member of the class being visited. + * + * @param name + * the internal name of an inner class (see + * {@link Type#getInternalName() getInternalName}). + * @param outerName + * the internal name of the class to which the inner class + * belongs (see {@link Type#getInternalName() getInternalName}). + * May be <tt>null</tt> for not member classes. + * @param innerName + * the (simple) name of the inner class inside its enclosing + * class. May be <tt>null</tt> for anonymous inner classes. + * @param access + * the access flags of the inner class as originally declared in + * the enclosing class. + */ + public void visitInnerClass(String name, String outerName, + String innerName, int access) { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * Visits a field of the class. + * + * @param access + * the field's access flags (see {@link Opcodes}). This parameter + * also indicates if the field is synthetic and/or deprecated. + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type Type}). + * @param signature + * the field's signature. May be <tt>null</tt> if the field's + * type does not use generic types. + * @param value + * the field's initial value. This parameter, which may be + * <tt>null</tt> if the field does not have an initial value, + * must be an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String} (for <tt>int</tt>, + * <tt>float</tt>, <tt>long</tt> or <tt>String</tt> fields + * respectively). <i>This parameter is only used for static + * fields</i>. Its value is ignored for non static fields, which + * must be initialized through bytecode instructions in + * constructors or methods. + * @return a visitor to visit field annotations and attributes, or + * <tt>null</tt> if this class visitor is not interested in visiting + * these annotations and attributes. + */ + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + if (cv != null) { + return cv.visitField(access, name, desc, signature, value); + } + return null; + } + + /** + * Visits a method of the class. This method <i>must</i> return a new + * {@link MethodVisitor} instance (or <tt>null</tt>) each time it is called, + * i.e., it should not return a previously returned visitor. + * + * @param access + * the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param signature + * the method's signature. May be <tt>null</tt> if the method + * parameters, return type and exceptions do not use generic + * types. + * @param exceptions + * the internal names of the method's exception classes (see + * {@link Type#getInternalName() getInternalName}). May be + * <tt>null</tt>. + * @return an object to visit the byte code of the method, or <tt>null</tt> + * if this class visitor is not interested in visiting the code of + * this method. + */ + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + if (cv != null) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + return null; + } + + /** + * Visits the end of the class. This method, which is the last one to be + * called, is used to inform the visitor that all the fields and methods of + * the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java new file mode 100644 index 00000000..a3d3a2de --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java @@ -0,0 +1,1776 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A {@link ClassVisitor} that generates classes in bytecode form. More + * precisely this visitor generates a byte array conforming to the Java class + * file format. It can be used alone, to generate a Java class "from scratch", + * or with one or more {@link ClassReader ClassReader} and adapter class visitor + * to generate a modified class from one or more existing Java classes. + * + * @author Eric Bruneton + */ +public class ClassWriter extends ClassVisitor { + + /** + * Flag to automatically compute the maximum stack size and the maximum + * number of local variables of methods. If this flag is set, then the + * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the + * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod} + * method will be ignored, and computed automatically from the signature and + * the bytecode of each method. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * Flag to automatically compute the stack map frames of methods from + * scratch. If this flag is set, then the calls to the + * {@link MethodVisitor#visitFrame} method are ignored, and the stack map + * frames are recomputed from the methods bytecode. The arguments of the + * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and + * recomputed from the bytecode. In other words, computeFrames implies + * computeMaxs. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + /** + * Pseudo access flag to distinguish between the synthetic attribute and the + * synthetic access flag. + */ + static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; + + /** + * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC. + */ + static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE + / Opcodes.ACC_SYNTHETIC; + + /** + * The type of instructions without any argument. + */ + static final int NOARG_INSN = 0; + + /** + * The type of instructions with an signed byte argument. + */ + static final int SBYTE_INSN = 1; + + /** + * The type of instructions with an signed short argument. + */ + static final int SHORT_INSN = 2; + + /** + * The type of instructions with a local variable index argument. + */ + static final int VAR_INSN = 3; + + /** + * The type of instructions with an implicit local variable index argument. + */ + static final int IMPLVAR_INSN = 4; + + /** + * The type of instructions with a type descriptor argument. + */ + static final int TYPE_INSN = 5; + + /** + * The type of field and method invocations instructions. + */ + static final int FIELDORMETH_INSN = 6; + + /** + * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. + */ + static final int ITFMETH_INSN = 7; + + /** + * The type of the INVOKEDYNAMIC instruction. + */ + static final int INDYMETH_INSN = 8; + + /** + * The type of instructions with a 2 bytes bytecode offset label. + */ + static final int LABEL_INSN = 9; + + /** + * The type of instructions with a 4 bytes bytecode offset label. + */ + static final int LABELW_INSN = 10; + + /** + * The type of the LDC instruction. + */ + static final int LDC_INSN = 11; + + /** + * The type of the LDC_W and LDC2_W instructions. + */ + static final int LDCW_INSN = 12; + + /** + * The type of the IINC instruction. + */ + static final int IINC_INSN = 13; + + /** + * The type of the TABLESWITCH instruction. + */ + static final int TABL_INSN = 14; + + /** + * The type of the LOOKUPSWITCH instruction. + */ + static final int LOOK_INSN = 15; + + /** + * The type of the MULTIANEWARRAY instruction. + */ + static final int MANA_INSN = 16; + + /** + * The type of the WIDE instruction. + */ + static final int WIDE_INSN = 17; + + /** + * The instruction types of all JVM opcodes. + */ + static final byte[] TYPE; + + /** + * The type of CONSTANT_Class constant pool items. + */ + static final int CLASS = 7; + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + static final int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + static final int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + static final int IMETH = 11; + + /** + * The type of CONSTANT_String constant pool items. + */ + static final int STR = 8; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + static final int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + static final int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + static final int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + static final int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + static final int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + static final int UTF8 = 1; + + /** + * The type of CONSTANT_MethodType constant pool items. + */ + static final int MTYPE = 16; + + /** + * The type of CONSTANT_MethodHandle constant pool items. + */ + static final int HANDLE = 15; + + /** + * The type of CONSTANT_InvokeDynamic constant pool items. + */ + static final int INDY = 18; + + /** + * The base value for all CONSTANT_MethodHandle constant pool items. + * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9 + * different items. + */ + static final int HANDLE_BASE = 20; + + /** + * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_NORMAL = 30; + + /** + * Uninitialized type Item stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. + */ + static final int TYPE_UNINIT = 31; + + /** + * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_MERGED = 32; + + /** + * The type of BootstrapMethods items. These items are stored in a special + * class attribute named BootstrapMethods and not in the constant pool. + */ + static final int BSM = 33; + + /** + * The class reader from which this class writer was constructed, if any. + */ + ClassReader cr; + + /** + * Minor and major version numbers of the class to be generated. + */ + int version; + + /** + * Index of the next item to be added in the constant pool. + */ + int index; + + /** + * The constant pool of this class. + */ + final ByteVector pool; + + /** + * The constant pool's hash table data. + */ + Item[] items; + + /** + * The threshold of the constant pool's hash table. + */ + int threshold; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key2; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key3; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key4; + + /** + * A type table used to temporarily store internal names that will not + * necessarily be stored in the constant pool. This type table is used by + * the control flow and data flow analysis algorithm used to compute stack + * map frames from scratch. This array associates to each index <tt>i</tt> + * the Item whose index is <tt>i</tt>. All Item objects stored in this array + * are also stored in the {@link #items} hash table. These two arrays allow + * to retrieve an Item from its index or, conversely, to get the index of an + * Item from its value. Each Item stores an internal name in its + * {@link Item#strVal1} field. + */ + Item[] typeTable; + + /** + * Number of elements in the {@link #typeTable} array. + */ + private short typeCount; + + /** + * The access flags of this class. + */ + private int access; + + /** + * The constant pool item that contains the internal name of this class. + */ + private int name; + + /** + * The internal name of this class. + */ + String thisName; + + /** + * The constant pool item that contains the signature of this class. + */ + private int signature; + + /** + * The constant pool item that contains the internal name of the super class + * of this class. + */ + private int superName; + + /** + * Number of interfaces implemented or extended by this class or interface. + */ + private int interfaceCount; + + /** + * The interfaces implemented or extended by this class or interface. More + * precisely, this array contains the indexes of the constant pool items + * that contain the internal names of these interfaces. + */ + private int[] interfaces; + + /** + * The index of the constant pool item that contains the name of the source + * file from which this class was compiled. + */ + private int sourceFile; + + /** + * The SourceDebug attribute of this class. + */ + private ByteVector sourceDebug; + + /** + * The constant pool item that contains the name of the enclosing class of + * this class. + */ + private int enclosingMethodOwner; + + /** + * The constant pool item that contains the name and descriptor of the + * enclosing method of this class. + */ + private int enclosingMethod; + + /** + * The runtime visible annotations of this class. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this class. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible type annotations of this class. + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this class. + */ + private AnnotationWriter itanns; + + /** + * The non standard attributes of this class. + */ + private Attribute attrs; + + /** + * The number of entries in the InnerClasses attribute. + */ + private int innerClassesCount; + + /** + * The InnerClasses attribute. + */ + private ByteVector innerClasses; + + /** + * The number of entries in the BootstrapMethods attribute. + */ + int bootstrapMethodsCount; + + /** + * The BootstrapMethods attribute. + */ + ByteVector bootstrapMethods; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the first element of this + * list. + */ + FieldWriter firstField; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the last element of this + * list. + */ + FieldWriter lastField; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the first element of + * this list. + */ + MethodWriter firstMethod; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the last element of this + * list. + */ + MethodWriter lastMethod; + + /** + * <tt>true</tt> if the maximum stack size and number of local variables + * must be automatically computed. + */ + private boolean computeMaxs; + + /** + * <tt>true</tt> if the stack map frames must be recomputed from scratch. + */ + private boolean computeFrames; + + /** + * <tt>true</tt> if the stack map tables of this class are invalid. The + * {@link MethodWriter#resizeInstructions} method cannot transform existing + * stack map tables, and so produces potentially invalid classes when it is + * executed. In this case the class is reread and rewritten with the + * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize + * stack map tables when this option is used). + */ + boolean invalidFrames; + + // ------------------------------------------------------------------------ + // Static initializer + // ------------------------------------------------------------------------ + + /** + * Computes the instruction types of JVM opcodes. + */ + static { + int i; + byte[] b = new byte[220]; + String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + for (i = 0; i < b.length; ++i) { + b[i] = (byte) (s.charAt(i) - 'A'); + } + TYPE = b; + + // code to generate the above string + // + // // SBYTE_INSN instructions + // b[Constants.NEWARRAY] = SBYTE_INSN; + // b[Constants.BIPUSH] = SBYTE_INSN; + // + // // SHORT_INSN instructions + // b[Constants.SIPUSH] = SHORT_INSN; + // + // // (IMPL)VAR_INSN instructions + // b[Constants.RET] = VAR_INSN; + // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3 + // b[i] = IMPLVAR_INSN; + // } + // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3 + // b[i] = IMPLVAR_INSN; + // } + // + // // TYPE_INSN instructions + // b[Constants.NEW] = TYPE_INSN; + // b[Constants.ANEWARRAY] = TYPE_INSN; + // b[Constants.CHECKCAST] = TYPE_INSN; + // b[Constants.INSTANCEOF] = TYPE_INSN; + // + // // (Set)FIELDORMETH_INSN instructions + // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) { + // b[i] = FIELDORMETH_INSN; + // } + // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN; + // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN; + // + // // LABEL(W)_INSN instructions + // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) { + // b[i] = LABEL_INSN; + // } + // b[Constants.IFNULL] = LABEL_INSN; + // b[Constants.IFNONNULL] = LABEL_INSN; + // b[200] = LABELW_INSN; // GOTO_W + // b[201] = LABELW_INSN; // JSR_W + // // temporary opcodes used internally by ASM - see Label and + // MethodWriter + // for (i = 202; i < 220; ++i) { + // b[i] = LABEL_INSN; + // } + // + // // LDC(_W) instructions + // b[Constants.LDC] = LDC_INSN; + // b[19] = LDCW_INSN; // LDC_W + // b[20] = LDCW_INSN; // LDC2_W + // + // // special instructions + // b[Constants.IINC] = IINC_INSN; + // b[Constants.TABLESWITCH] = TABL_INSN; + // b[Constants.LOOKUPSWITCH] = LOOK_INSN; + // b[Constants.MULTIANEWARRAY] = MANA_INSN; + // b[196] = WIDE_INSN; // WIDE + // + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('A' + b[i])); + // } + // System.err.println(); + } + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #COMPUTE_MAXS}, + * {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + super(Opcodes.ASM5); + index = 1; + pool = new ByteVector(); + items = new Item[256]; + threshold = (int) (0.75d * items.length); + key = new Item(); + key2 = new Item(); + key3 = new Item(); + key4 = new Item(); + this.computeMaxs = (flags & COMPUTE_MAXS) != 0; + this.computeFrames = (flags & COMPUTE_FRAMES) != 0; + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for + * "mostly add" bytecode transformations. These optimizations are the + * following: + * + * <ul> + * <li>The constant pool from the original class is copied as is in the new + * class, which saves time. New constant pool entries will be added at the + * end if necessary, but unused constant pool entries <i>won't be + * removed</i>.</li> + * <li>Methods that are not transformed are copied as is in the new class, + * directly from the original class bytecode (i.e. without emitting visit + * events for all the method instructions), which saves a <i>lot</i> of + * time. Untransformed methods are detected by the fact that the + * {@link ClassReader} receives {@link MethodVisitor} objects that come from + * a {@link ClassWriter} (and not from any other {@link ClassVisitor} + * instance).</li> + * </ul> + * + * @param classReader + * the {@link ClassReader} used to read the original class. It + * will be used to copy the entire constant pool from the + * original class and also to copy other fragments of original + * bytecode where applicable. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. <i>These option flags do not affect methods + * that are copied as is in the new class. This means that the + * maximum stack size nor the stack frames will be computed for + * these methods</i>. See {@link #COMPUTE_MAXS}, + * {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + this(flags); + classReader.copyPool(this); + this.cr = classReader; + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public final void visit(final int version, final int access, + final String name, final String signature, final String superName, + final String[] interfaces) { + this.version = version; + this.access = access; + this.name = newClass(name); + thisName = name; + if (ClassReader.SIGNATURES && signature != null) { + this.signature = newUTF8(signature); + } + this.superName = superName == null ? 0 : newClass(superName); + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = newClass(interfaces[i]); + } + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFile = newUTF8(file); + } + if (debug != null) { + sourceDebug = new ByteVector().encodeUTF8(debug, 0, + Integer.MAX_VALUE); + } + } + + @Override + public final void visitOuterClass(final String owner, final String name, + final String desc) { + enclosingMethodOwner = newClass(owner); + if (name != null && desc != null) { + enclosingMethod = newNameType(name, desc); + } + } + + @Override + public final AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public final AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + + @Override + public final void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public final void visitInnerClass(final String name, + final String outerName, final String innerName, final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + // Sec. 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the + // constant_pool table which represents a class or interface C that is + // not a package member must have exactly one corresponding entry in the + // classes array". To avoid duplicates we keep track in the intVal field + // of the Item of each CONSTANT_Class_info entry C whether an inner + // class entry has already been added for C (this field is unused for + // class entries, and changing its value does not change the hashcode + // and equality tests). If so we store the index of this inner class + // entry (plus one) in intVal. This hack allows duplicate detection in + // O(1) time. + Item nameItem = newClassItem(name); + if (nameItem.intVal == 0) { + ++innerClassesCount; + innerClasses.putShort(nameItem.index); + innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); + innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); + innerClasses.putShort(access); + nameItem.intVal = innerClassesCount; + } else { + // Compare the inner classes entry nameItem.intVal - 1 with the + // arguments of this method and throw an exception if there is a + // difference? + } + } + + @Override + public final FieldVisitor visitField(final int access, final String name, + final String desc, final String signature, final Object value) { + return new FieldWriter(this, access, name, desc, signature, value); + } + + @Override + public final MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, final String[] exceptions) { + return new MethodWriter(this, access, name, desc, signature, + exceptions, computeMaxs, computeFrames); + } + + @Override + public final void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Other public methods + // ------------------------------------------------------------------------ + + /** + * Returns the bytecode of the class that was build with this class writer. + * + * @return the bytecode of the class that was build with this class writer. + */ + public byte[] toByteArray() { + if (index > 0xFFFF) { + throw new RuntimeException("Class file too large!"); + } + // computes the real size of the bytecode of this class + int size = 24 + 2 * interfaceCount; + int nbFields = 0; + FieldWriter fb = firstField; + while (fb != null) { + ++nbFields; + size += fb.getSize(); + fb = (FieldWriter) fb.fv; + } + int nbMethods = 0; + MethodWriter mb = firstMethod; + while (mb != null) { + ++nbMethods; + size += mb.getSize(); + mb = (MethodWriter) mb.mv; + } + int attributeCount = 0; + if (bootstrapMethods != null) { + // we put it as first attribute in order to improve a bit + // ClassReader.copyBootstrapMethods + ++attributeCount; + size += 8 + bootstrapMethods.length; + newUTF8("BootstrapMethods"); + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + size += 8; + newUTF8("Signature"); + } + if (sourceFile != 0) { + ++attributeCount; + size += 8; + newUTF8("SourceFile"); + } + if (sourceDebug != null) { + ++attributeCount; + size += sourceDebug.length + 6; + newUTF8("SourceDebugExtension"); + } + if (enclosingMethodOwner != 0) { + ++attributeCount; + size += 10; + newUTF8("EnclosingMethod"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + size += 6; + newUTF8("Deprecated"); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((version & 0xFFFF) < Opcodes.V1_5 + || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + size += 6; + newUTF8("Synthetic"); + } + } + if (innerClasses != null) { + ++attributeCount; + size += 8 + innerClasses.length; + newUTF8("InnerClasses"); + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + size += 8 + anns.getSize(); + newUTF8("RuntimeVisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + size += 8 + ianns.getSize(); + newUTF8("RuntimeInvisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + size += 8 + tanns.getSize(); + newUTF8("RuntimeVisibleTypeAnnotations"); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + size += 8 + itanns.getSize(); + newUTF8("RuntimeInvisibleTypeAnnotations"); + } + if (attrs != null) { + attributeCount += attrs.getCount(); + size += attrs.getSize(this, null, 0, -1, -1); + } + size += pool.length; + // allocates a byte vector of this size, in order to avoid unnecessary + // arraycopy operations in the ByteVector.enlarge() method + ByteVector out = new ByteVector(size); + out.putInt(0xCAFEBABE).putInt(version); + out.putShort(index).putByteArray(pool.data, 0, pool.length); + int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE + | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC); + out.putShort(access & ~mask).putShort(name).putShort(superName); + out.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + out.putShort(interfaces[i]); + } + out.putShort(nbFields); + fb = firstField; + while (fb != null) { + fb.put(out); + fb = (FieldWriter) fb.fv; + } + out.putShort(nbMethods); + mb = firstMethod; + while (mb != null) { + mb.put(out); + mb = (MethodWriter) mb.mv; + } + out.putShort(attributeCount); + if (bootstrapMethods != null) { + out.putShort(newUTF8("BootstrapMethods")); + out.putInt(bootstrapMethods.length + 2).putShort( + bootstrapMethodsCount); + out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); + } + if (sourceFile != 0) { + out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); + } + if (sourceDebug != null) { + int len = sourceDebug.length; + out.putShort(newUTF8("SourceDebugExtension")).putInt(len); + out.putByteArray(sourceDebug.data, 0, len); + } + if (enclosingMethodOwner != 0) { + out.putShort(newUTF8("EnclosingMethod")).putInt(4); + out.putShort(enclosingMethodOwner).putShort(enclosingMethod); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(newUTF8("Deprecated")).putInt(0); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((version & 0xFFFF) < Opcodes.V1_5 + || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(newUTF8("Synthetic")).putInt(0); + } + } + if (innerClasses != null) { + out.putShort(newUTF8("InnerClasses")); + out.putInt(innerClasses.length + 2).putShort(innerClassesCount); + out.putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } + if (attrs != null) { + attrs.put(this, null, 0, -1, -1, out); + } + if (invalidFrames) { + anns = null; + ianns = null; + attrs = null; + innerClassesCount = 0; + innerClasses = null; + bootstrapMethodsCount = 0; + bootstrapMethods = null; + firstField = null; + lastField = null; + firstMethod = null; + lastMethod = null; + computeMaxs = false; + computeFrames = true; + invalidFrames = false; + new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES); + return toByteArray(); + } + return out.data; + } + + // ------------------------------------------------------------------------ + // Utility methods: constant pool management + // ------------------------------------------------------------------------ + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * + * @param cst + * the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double}, a {@link String} or a + * {@link Type}. + * @return a new or already existing constant item with the given value. + */ + Item newConstItem(final Object cst) { + if (cst instanceof Integer) { + int val = ((Integer) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Byte) { + int val = ((Byte) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Character) { + int val = ((Character) cst).charValue(); + return newInteger(val); + } else if (cst instanceof Short) { + int val = ((Short) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Boolean) { + int val = ((Boolean) cst).booleanValue() ? 1 : 0; + return newInteger(val); + } else if (cst instanceof Float) { + float val = ((Float) cst).floatValue(); + return newFloat(val); + } else if (cst instanceof Long) { + long val = ((Long) cst).longValue(); + return newLong(val); + } else if (cst instanceof Double) { + double val = ((Double) cst).doubleValue(); + return newDouble(val); + } else if (cst instanceof String) { + return newString((String) cst); + } else if (cst instanceof Type) { + Type t = (Type) cst; + int s = t.getSort(); + if (s == Type.OBJECT) { + return newClassItem(t.getInternalName()); + } else if (s == Type.METHOD) { + return newMethodTypeItem(t.getDescriptor()); + } else { // s == primitive type or array + return newClassItem(t.getDescriptor()); + } + } else if (cst instanceof Handle) { + Handle h = (Handle) cst; + return newHandleItem(h.tag, h.owner, h.name, h.desc); + } else { + throw new IllegalArgumentException("value " + cst); + } + } + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param cst + * the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the + * given value. + */ + public int newConst(final Object cst) { + return newConstItem(cst).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. <i>This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters.</i> + * + * @param value + * the String value. + * @return the index of a new or already existing UTF8 item. + */ + public int newUTF8(final String value) { + key.set(UTF8, value, null, null); + Item result = get(key); + if (result == null) { + pool.putByte(UTF8).putUTF8(value); + result = new Item(index++, key); + put(result); + } + return result.index; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param value + * the internal name of the class. + * @return a new or already existing class reference item. + */ + Item newClassItem(final String value) { + key2.set(CLASS, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(CLASS, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param value + * the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return newClassItem(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param methodDesc + * method descriptor of the method type. + * @return a new or already existing method type reference item. + */ + Item newMethodTypeItem(final String methodDesc) { + key2.set(MTYPE, methodDesc, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(MTYPE, newUTF8(methodDesc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param methodDesc + * method descriptor of the method type. + * @return the index of a new or already existing method type reference + * item. + */ + public int newMethodType(final String methodDesc) { + return newMethodTypeItem(methodDesc).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. <i>This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters.</i> + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @return a new or an already existing method type reference item. + */ + Item newHandleItem(final int tag, final String owner, final String name, + final String desc) { + key4.set(HANDLE_BASE + tag, owner, name, desc); + Item result = get(key4); + if (result == null) { + if (tag <= Opcodes.H_PUTSTATIC) { + put112(HANDLE, tag, newField(owner, name, desc)); + } else { + put112(HANDLE, + tag, + newMethod(owner, name, desc, + tag == Opcodes.H_INVOKEINTERFACE)); + } + result = new Item(index++, key4); + put(result); + } + return result; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. <i>This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters.</i> + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @return the index of a new or already existing method type reference + * item. + */ + public int newHandle(final int tag, final String owner, final String name, + final String desc) { + return newHandleItem(tag, owner, name, desc).index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param name + * name of the invoked method. + * @param desc + * descriptor of the invoke method. + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. + * + * @return a new or an already existing invokedynamic type reference item. + */ + Item newInvokeDynamicItem(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + // cache for performance + ByteVector bootstrapMethods = this.bootstrapMethods; + if (bootstrapMethods == null) { + bootstrapMethods = this.bootstrapMethods = new ByteVector(); + } + + int position = bootstrapMethods.length; // record current position + + int hashCode = bsm.hashCode(); + bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name, + bsm.desc)); + + int argsLength = bsmArgs.length; + bootstrapMethods.putShort(argsLength); + + for (int i = 0; i < argsLength; i++) { + Object bsmArg = bsmArgs[i]; + hashCode ^= bsmArg.hashCode(); + bootstrapMethods.putShort(newConst(bsmArg)); + } + + byte[] data = bootstrapMethods.data; + int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments) + hashCode &= 0x7FFFFFFF; + Item result = items[hashCode % items.length]; + loop: while (result != null) { + if (result.type != BSM || result.hashCode != hashCode) { + result = result.next; + continue; + } + + // because the data encode the size of the argument + // we don't need to test if these size are equals + int resultPosition = result.intVal; + for (int p = 0; p < length; p++) { + if (data[position + p] != data[resultPosition + p]) { + result = result.next; + continue loop; + } + } + break; + } + + int bootstrapMethodIndex; + if (result != null) { + bootstrapMethodIndex = result.index; + bootstrapMethods.length = position; // revert to old position + } else { + bootstrapMethodIndex = bootstrapMethodsCount++; + result = new Item(bootstrapMethodIndex); + result.set(position, hashCode); + put(result); + } + + // now, create the InvokeDynamic constant + key3.set(name, desc, bootstrapMethodIndex); + result = get(key3); + if (result == null) { + put122(INDY, bootstrapMethodIndex, newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param name + * name of the invoked method. + * @param desc + * descriptor of the invoke method. + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. + * + * @return the index of a new or already existing invokedynamic reference + * item. + */ + public int newInvokeDynamic(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner + * the internal name of the field's owner class. + * @param name + * the field's name. + * @param desc + * the field's descriptor. + * @return a new or already existing field reference item. + */ + Item newFieldItem(final String owner, final String name, final String desc) { + key3.set(FIELD, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(FIELD, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param owner + * the internal name of the field's owner class. + * @param name + * the field's name. + * @param desc + * the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String desc) { + return newFieldItem(owner, name, desc).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner + * the internal name of the method's owner class. + * @param name + * the method's name. + * @param desc + * the method's descriptor. + * @param itf + * <tt>true</tt> if <tt>owner</tt> is an interface. + * @return a new or already existing method reference item. + */ + Item newMethodItem(final String owner, final String name, + final String desc, final boolean itf) { + int type = itf ? IMETH : METH; + key3.set(type, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(type, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * <i>This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters.</i> + * + * @param owner + * the internal name of the method's owner class. + * @param name + * the method's name. + * @param desc + * the method's descriptor. + * @param itf + * <tt>true</tt> if <tt>owner</tt> is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod(final String owner, final String name, + final String desc, final boolean itf) { + return newMethodItem(owner, name, desc, itf).index; + } + + /** + * Adds an integer to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param value + * the int value. + * @return a new or already existing int item. + */ + Item newInteger(final int value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(INT).putInt(value); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a float to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the float value. + * @return a new or already existing float item. + */ + Item newFloat(final float value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(FLOAT).putInt(key.intVal); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a long to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the long value. + * @return a new or already existing long item. + */ + Item newLong(final long value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(LONG).putLong(value); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a double to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the double value. + * @return a new or already existing double item. + */ + Item newDouble(final double value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(DOUBLE).putLong(key.longVal); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a string to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the String value. + * @return a new or already existing string item. + */ + private Item newString(final String value) { + key2.set(STR, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(STR, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. <i>This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters.</i> + * + * @param name + * a name. + * @param desc + * a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String desc) { + return newNameTypeItem(name, desc).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param name + * a name. + * @param desc + * a type descriptor. + * @return a new or already existing name and type item. + */ + Item newNameTypeItem(final String name, final String desc) { + key2.set(NAME_TYPE, name, desc, null); + Item result = get(key2); + if (result == null) { + put122(NAME_TYPE, newUTF8(name), newUTF8(desc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds the given internal name to {@link #typeTable} and returns its index. + * Does nothing if the type table already contains this internal name. + * + * @param type + * the internal name to be added to the type table. + * @return the index of this internal name in the type table. + */ + int addType(final String type) { + key.set(TYPE_NORMAL, type, null, null); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given "uninitialized" type to {@link #typeTable} and returns its + * index. This method is used for UNINITIALIZED types, made of an internal + * name and a bytecode offset. + * + * @param type + * the internal name to be added to the type table. + * @param offset + * the bytecode offset of the NEW instruction that created this + * UNINITIALIZED type value. + * @return the index of this internal name in the type table. + */ + int addUninitializedType(final String type, final int offset) { + key.type = TYPE_UNINIT; + key.intVal = offset; + key.strVal1 = type; + key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given Item to {@link #typeTable}. + * + * @param item + * the value to be added to the type table. + * @return the added Item, which a new Item instance with the same value as + * the given Item. + */ + private Item addType(final Item item) { + ++typeCount; + Item result = new Item(typeCount, key); + put(result); + if (typeTable == null) { + typeTable = new Item[16]; + } + if (typeCount == typeTable.length) { + Item[] newTable = new Item[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); + typeTable = newTable; + } + typeTable[typeCount] = result; + return result; + } + + /** + * Returns the index of the common super type of the two given types. This + * method calls {@link #getCommonSuperClass} and caches the result in the + * {@link #items} hash table to speedup future calls with the same + * parameters. + * + * @param type1 + * index of an internal name in {@link #typeTable}. + * @param type2 + * index of an internal name in {@link #typeTable}. + * @return the index of the common super type of the two given types. + */ + int getMergedType(final int type1, final int type2) { + key2.type = TYPE_MERGED; + key2.longVal = type1 | (((long) type2) << 32); + key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2); + Item result = get(key2); + if (result == null) { + String t = typeTable[type1].strVal1; + String u = typeTable[type2].strVal1; + key2.intVal = addType(getCommonSuperClass(t, u)); + result = new Item((short) 0, key2); + put(result); + } + return result.intVal; + } + + /** + * Returns the common super type of the two given types. The default + * implementation of this method <i>loads</i> the two given classes and uses + * the java.lang.Class methods to find the common super class. It can be + * overridden to compute this common super type in other ways, in particular + * without actually loading any class, or to take into account the class + * that is currently being generated by this ClassWriter, which can of + * course not be loaded since it is under construction. + * + * @param type1 + * the internal name of a class. + * @param type2 + * the internal name of another class. + * @return the internal name of the common super class of the two given + * classes. + */ + protected String getCommonSuperClass(final String type1, final String type2) { + Class<?> c, d; + ClassLoader classLoader = getClass().getClassLoader(); + try { + c = Class.forName(type1.replace('/', '.'), false, classLoader); + d = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + + /** + * Returns the constant pool's hash table item which is equal to the given + * item. + * + * @param key + * a constant pool item. + * @return the constant pool's hash table item which is equal to the given + * item, or <tt>null</tt> if there is no such item. + */ + private Item get(final Item key) { + Item i = items[key.hashCode % items.length]; + while (i != null && (i.type != key.type || !key.isEqualTo(i))) { + i = i.next; + } + return i; + } + + /** + * Puts the given item in the constant pool's hash table. The hash table + * <i>must</i> not already contains this item. + * + * @param i + * the item to be added to the constant pool's hash table. + */ + private void put(final Item i) { + if (index + typeCount > threshold) { + int ll = items.length; + int nl = ll * 2 + 1; + Item[] newItems = new Item[nl]; + for (int l = ll - 1; l >= 0; --l) { + Item j = items[l]; + while (j != null) { + int index = j.hashCode % newItems.length; + Item k = j.next; + j.next = newItems[index]; + newItems[index] = j; + j = k; + } + } + items = newItems; + threshold = (int) (nl * 0.75); + } + int index = i.hashCode % items.length; + i.next = items[index]; + items[index] = i; + } + + /** + * Puts one byte and two shorts into the constant pool. + * + * @param b + * a byte. + * @param s1 + * a short. + * @param s2 + * another short. + */ + private void put122(final int b, final int s1, final int s2) { + pool.put12(b, s1).putShort(s2); + } + + /** + * Puts two bytes and one short into the constant pool. + * + * @param b1 + * a byte. + * @param b2 + * another byte. + * @param s + * a short. + */ + private void put112(final int b1, final int b2, final int s) { + pool.put11(b1, b2).putShort(s); + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Context.java b/spring-core/src/main/java/org/springframework/asm/Context.java new file mode 100644 index 00000000..cab4e23f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Context.java @@ -0,0 +1,145 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.springframework.asm; + +/** + * Information about a class being parsed in a {@link ClassReader}. + * + * @author Eric Bruneton + */ +class Context { + + /** + * Prototypes of the attributes that must be parsed for this class. + */ + Attribute[] attrs; + + /** + * The {@link ClassReader} option flags for the parsing of this class. + */ + int flags; + + /** + * The buffer used to read strings. + */ + char[] buffer; + + /** + * The start index of each bootstrap method. + */ + int[] bootstrapMethods; + + /** + * The access flags of the method currently being parsed. + */ + int access; + + /** + * The name of the method currently being parsed. + */ + String name; + + /** + * The descriptor of the method currently being parsed. + */ + String desc; + + /** + * The label objects, indexed by bytecode offset, of the method currently + * being parsed (only bytecode offsets for which a label is needed have a + * non null associated Label object). + */ + Label[] labels; + + /** + * The target of the type annotation currently being parsed. + */ + int typeRef; + + /** + * The path of the type annotation currently being parsed. + */ + TypePath typePath; + + /** + * The offset of the latest stack map frame that has been parsed. + */ + int offset; + + /** + * The labels corresponding to the start of the local variable ranges in the + * local variable type annotation currently being parsed. + */ + Label[] start; + + /** + * The labels corresponding to the end of the local variable ranges in the + * local variable type annotation currently being parsed. + */ + Label[] end; + + /** + * The local variable indices for each local variable range in the local + * variable type annotation currently being parsed. + */ + int[] index; + + /** + * The encoding of the latest stack map frame that has been parsed. + */ + int mode; + + /** + * The number of locals in the latest stack map frame that has been parsed. + */ + int localCount; + + /** + * The number locals in the latest stack map frame that has been parsed, + * minus the number of locals in the previous frame. + */ + int localDiff; + + /** + * The local values of the latest stack map frame that has been parsed. + */ + Object[] local; + + /** + * The stack size of the latest stack map frame that has been parsed. + */ + int stackCount; + + /** + * The stack values of the latest stack map frame that has been parsed. + */ + Object[] stack; +}
\ No newline at end of file diff --git a/spring-core/src/main/java/org/springframework/asm/Edge.java b/spring-core/src/main/java/org/springframework/asm/Edge.java new file mode 100644 index 00000000..196adc12 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Edge.java @@ -0,0 +1,75 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * An edge in the control flow graph of a method body. See {@link Label Label}. + * + * @author Eric Bruneton + */ +class Edge { + + /** + * Denotes a normal control flow graph edge. + */ + static final int NORMAL = 0; + + /** + * Denotes a control flow graph edge corresponding to an exception handler. + * More precisely any {@link Edge} whose {@link #info} is strictly positive + * corresponds to an exception handler. The actual value of {@link #info} is + * the index, in the {@link ClassWriter} type table, of the exception that + * is catched. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. If + * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative) + * stack size in the basic block from which this edge originates. This size + * is equal to the stack size at the "jump" instruction to which this edge + * corresponds, relatively to the stack size at the beginning of the + * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used, + * this field is the kind of this control flow graph edge (i.e. NORMAL or + * EXCEPTION). + */ + int info; + + /** + * The successor block of the basic block from which this edge originates. + */ + Label successor; + + /** + * The next edge in the list of successors of the originating basic block. + * See {@link Label#successors successors}. + */ + Edge next; +} diff --git a/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java new file mode 100644 index 00000000..2822f3b2 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java @@ -0,0 +1,152 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A visitor to visit a Java field. The methods of this class must be called in + * the following order: ( <tt>visitAnnotation</tt> | + * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* <tt>visitEnd</tt>. + * + * @author Eric Bruneton + */ +public abstract class FieldVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The field visitor to which this visitor must delegate method calls. May + * be null. + */ + protected FieldVisitor fv; + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param fv + * the field visitor to which this visitor must delegate method + * calls. May be null. + */ + public FieldVisitor(final int api, final FieldVisitor fv) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.fv = fv; + } + + /** + * Visits an annotation of the field. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (fv != null) { + return fv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation on the type of the field. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#FIELD FIELD}. See + * {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (fv != null) { + return fv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the field. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (fv != null) { + fv.visitAttribute(attr); + } + } + + /** + * Visits the end of the field. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/FieldWriter.java b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java new file mode 100644 index 00000000..f5a135e5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java @@ -0,0 +1,329 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * An {@link FieldVisitor} that generates Java fields in bytecode form. + * + * @author Eric Bruneton + */ +final class FieldWriter extends FieldVisitor { + + /** + * The class writer to which this field must be added. + */ + private final ClassWriter cw; + + /** + * Access flags of this field. + */ + private final int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * field. + */ + private final int desc; + + /** + * The index of the constant pool item that contains the signature of this + * field. + */ + private int signature; + + /** + * The index of the constant pool item that contains the constant value of + * this field. + */ + private int value; + + /** + * The runtime visible annotations of this field. May be <tt>null</tt>. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this field. May be <tt>null</tt>. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible type annotations of this field. May be <tt>null</tt>. + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this field. May be + * <tt>null</tt>. + */ + private AnnotationWriter itanns; + + /** + * The non standard attributes of this field. May be <tt>null</tt>. + */ + private Attribute attrs; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link FieldWriter}. + * + * @param cw + * the class writer to which this field must be added. + * @param access + * the field's access flags (see {@link Opcodes}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type}). + * @param signature + * the field's signature. May be <tt>null</tt>. + * @param value + * the field's constant value. May be <tt>null</tt>. + */ + FieldWriter(final ClassWriter cw, final int access, final String name, + final String desc, final String signature, final Object value) { + super(Opcodes.ASM5); + if (cw.firstField == null) { + cw.firstField = this; + } else { + cw.lastField.fv = this; + } + cw.lastField = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + if (ClassReader.SIGNATURES && signature != null) { + this.signature = cw.newUTF8(signature); + } + if (value != null) { + this.value = cw.newConstItem(value).index; + } + } + + // ------------------------------------------------------------------------ + // Implementation of the FieldVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this field. + * + * @return the size of this field. + */ + int getSize() { + int size = 8; + if (value != 0) { + cw.newUTF8("ConstantValue"); + size += 8; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + cw.newUTF8("Synthetic"); + size += 6; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != 0) { + cw.newUTF8("Signature"); + size += 8; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + tanns.getSize(); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + itanns.getSize(); + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the content of this field into the given byte vector. + * + * @param out + * where the content of this field must be put. + */ + void put(final ByteVector out) { + final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; + int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); + out.putShort(access & ~mask).putShort(name).putShort(desc); + int attributeCount = 0; + if (value != 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (value != 0) { + out.putShort(cw.newUTF8("ConstantValue")); + out.putInt(2).putShort(value); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(cw.newUTF8("Signature")); + out.putInt(2).putShort(signature); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Frame.java b/spring-core/src/main/java/org/springframework/asm/Frame.java new file mode 100644 index 00000000..29e71c5a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Frame.java @@ -0,0 +1,1462 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * Information about the input and output stack map frames of a basic block. + * + * @author Eric Bruneton + */ +final class Frame { + + /* + * Frames are computed in a two steps process: during the visit of each + * instruction, the state of the frame at the end of current basic block is + * updated by simulating the action of the instruction on the previous state + * of this so called "output frame". In visitMaxs, a fix point algorithm is + * used to compute the "input frame" of each basic block, i.e. the stack map + * frame at the beginning of the basic block, starting from the input frame + * of the first basic block (which is computed from the method descriptor), + * and by using the previously computed output frames to compute the input + * state of the other blocks. + * + * All output and input frames are stored as arrays of integers. Reference + * and array types are represented by an index into a type table (which is + * not the same as the constant pool of the class, in order to avoid adding + * unnecessary constants in the pool - not all computed frames will end up + * being stored in the stack map table). This allows very fast type + * comparisons. + * + * Output stack map frames are computed relatively to the input frame of the + * basic block, which is not yet known when output frames are computed. It + * is therefore necessary to be able to represent abstract types such as + * "the type at position x in the input frame locals" or "the type at + * position x from the top of the input frame stack" or even "the type at + * position x in the input frame, with y more (or less) array dimensions". + * This explains the rather complicated type format used in output frames. + * + * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a + * signed number of array dimensions (from -8 to 7). KIND is either BASE, + * LOCAL or STACK. BASE is used for types that are not relative to the input + * frame. LOCAL is used for types that are relative to the input local + * variable types. STACK is used for types that are relative to the input + * stack types. VALUE depends on KIND. For LOCAL types, it is an index in + * the input local variable types. For STACK types, it is a position + * relatively to the top of input frame stack. For BASE types, it is either + * one of the constants defined below, or for OBJECT and UNINITIALIZED + * types, a tag and an index in the type table. + * + * Output frames can contain types of any kind and with a positive or + * negative dimension (and even unassigned types, represented by 0 - which + * does not correspond to any valid type value). Input frames can only + * contain BASE types of positive or null dimension. In all cases the type + * table contains only internal type names (array type descriptors are + * forbidden - dimensions must be represented through the DIM field). + * + * The LONG and DOUBLE types are always represented by using two slots (LONG + * + TOP or DOUBLE + TOP), for local variable types as well as in the + * operand stack. This is necessary to be able to simulate DUPx_y + * instructions, whose effect would be dependent on the actual type values + * if types were always represented by a single slot in the stack (and this + * is not possible, since actual type values are not always known - cf LOCAL + * and STACK type kinds). + */ + + /** + * Mask to get the dimension of a frame type. This dimension is a signed + * integer between -8 and 7. + */ + static final int DIM = 0xF0000000; + + /** + * Constant to be added to a type to get a type with one more dimension. + */ + static final int ARRAY_OF = 0x10000000; + + /** + * Constant to be added to a type to get a type with one less dimension. + */ + static final int ELEMENT_OF = 0xF0000000; + + /** + * Mask to get the kind of a frame type. + * + * @see #BASE + * @see #LOCAL + * @see #STACK + */ + static final int KIND = 0xF000000; + + /** + * Flag used for LOCAL and STACK types. Indicates that if this type happens + * to be a long or double type (during the computations of input frames), + * then it must be set to TOP because the second word of this value has been + * reused to store other data in the basic block. Hence the first word no + * longer stores a valid long or double value. + */ + static final int TOP_IF_LONG_OR_DOUBLE = 0x800000; + + /** + * Mask to get the value of a frame type. + */ + static final int VALUE = 0x7FFFFF; + + /** + * Mask to get the kind of base types. + */ + static final int BASE_KIND = 0xFF00000; + + /** + * Mask to get the value of base types. + */ + static final int BASE_VALUE = 0xFFFFF; + + /** + * Kind of the types that are not relative to an input stack map frame. + */ + static final int BASE = 0x1000000; + + /** + * Base kind of the base reference types. The BASE_VALUE of such types is an + * index into the type table. + */ + static final int OBJECT = BASE | 0x700000; + + /** + * Base kind of the uninitialized base types. The BASE_VALUE of such types + * in an index into the type table (the Item at that index contains both an + * instruction offset and an internal class name). + */ + static final int UNINITIALIZED = BASE | 0x800000; + + /** + * Kind of the types that are relative to the local variable types of an + * input stack map frame. The value of such types is a local variable index. + */ + private static final int LOCAL = 0x2000000; + + /** + * Kind of the the types that are relative to the stack of an input stack + * map frame. The value of such types is a position relatively to the top of + * this stack. + */ + private static final int STACK = 0x3000000; + + /** + * The TOP type. This is a BASE type. + */ + static final int TOP = BASE | 0; + + /** + * The BOOLEAN type. This is a BASE type mainly used for array types. + */ + static final int BOOLEAN = BASE | 9; + + /** + * The BYTE type. This is a BASE type mainly used for array types. + */ + static final int BYTE = BASE | 10; + + /** + * The CHAR type. This is a BASE type mainly used for array types. + */ + static final int CHAR = BASE | 11; + + /** + * The SHORT type. This is a BASE type mainly used for array types. + */ + static final int SHORT = BASE | 12; + + /** + * The INTEGER type. This is a BASE type. + */ + static final int INTEGER = BASE | 1; + + /** + * The FLOAT type. This is a BASE type. + */ + static final int FLOAT = BASE | 2; + + /** + * The DOUBLE type. This is a BASE type. + */ + static final int DOUBLE = BASE | 3; + + /** + * The LONG type. This is a BASE type. + */ + static final int LONG = BASE | 4; + + /** + * The NULL type. This is a BASE type. + */ + static final int NULL = BASE | 5; + + /** + * The UNINITIALIZED_THIS type. This is a BASE type. + */ + static final int UNINITIALIZED_THIS = BASE | 6; + + /** + * The stack size variation corresponding to each JVM instruction. This + * stack variation is equal to the size of the values produced by an + * instruction, minus the size of the values consumed by this instruction. + */ + static final int[] SIZE; + + /** + * Computes the stack size variation corresponding to each JVM instruction. + */ + static { + int i; + int[] b = new int[202]; + String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" + + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" + + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" + + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; + for (i = 0; i < b.length; ++i) { + b[i] = s.charAt(i) - 'E'; + } + SIZE = b; + + // code to generate the above string + // + // int NA = 0; // not applicable (unused opcode or variable size opcode) + // + // b = new int[] { + // 0, //NOP, // visitInsn + // 1, //ACONST_NULL, // - + // 1, //ICONST_M1, // - + // 1, //ICONST_0, // - + // 1, //ICONST_1, // - + // 1, //ICONST_2, // - + // 1, //ICONST_3, // - + // 1, //ICONST_4, // - + // 1, //ICONST_5, // - + // 2, //LCONST_0, // - + // 2, //LCONST_1, // - + // 1, //FCONST_0, // - + // 1, //FCONST_1, // - + // 1, //FCONST_2, // - + // 2, //DCONST_0, // - + // 2, //DCONST_1, // - + // 1, //BIPUSH, // visitIntInsn + // 1, //SIPUSH, // - + // 1, //LDC, // visitLdcInsn + // NA, //LDC_W, // - + // NA, //LDC2_W, // - + // 1, //ILOAD, // visitVarInsn + // 2, //LLOAD, // - + // 1, //FLOAD, // - + // 2, //DLOAD, // - + // 1, //ALOAD, // - + // NA, //ILOAD_0, // - + // NA, //ILOAD_1, // - + // NA, //ILOAD_2, // - + // NA, //ILOAD_3, // - + // NA, //LLOAD_0, // - + // NA, //LLOAD_1, // - + // NA, //LLOAD_2, // - + // NA, //LLOAD_3, // - + // NA, //FLOAD_0, // - + // NA, //FLOAD_1, // - + // NA, //FLOAD_2, // - + // NA, //FLOAD_3, // - + // NA, //DLOAD_0, // - + // NA, //DLOAD_1, // - + // NA, //DLOAD_2, // - + // NA, //DLOAD_3, // - + // NA, //ALOAD_0, // - + // NA, //ALOAD_1, // - + // NA, //ALOAD_2, // - + // NA, //ALOAD_3, // - + // -1, //IALOAD, // visitInsn + // 0, //LALOAD, // - + // -1, //FALOAD, // - + // 0, //DALOAD, // - + // -1, //AALOAD, // - + // -1, //BALOAD, // - + // -1, //CALOAD, // - + // -1, //SALOAD, // - + // -1, //ISTORE, // visitVarInsn + // -2, //LSTORE, // - + // -1, //FSTORE, // - + // -2, //DSTORE, // - + // -1, //ASTORE, // - + // NA, //ISTORE_0, // - + // NA, //ISTORE_1, // - + // NA, //ISTORE_2, // - + // NA, //ISTORE_3, // - + // NA, //LSTORE_0, // - + // NA, //LSTORE_1, // - + // NA, //LSTORE_2, // - + // NA, //LSTORE_3, // - + // NA, //FSTORE_0, // - + // NA, //FSTORE_1, // - + // NA, //FSTORE_2, // - + // NA, //FSTORE_3, // - + // NA, //DSTORE_0, // - + // NA, //DSTORE_1, // - + // NA, //DSTORE_2, // - + // NA, //DSTORE_3, // - + // NA, //ASTORE_0, // - + // NA, //ASTORE_1, // - + // NA, //ASTORE_2, // - + // NA, //ASTORE_3, // - + // -3, //IASTORE, // visitInsn + // -4, //LASTORE, // - + // -3, //FASTORE, // - + // -4, //DASTORE, // - + // -3, //AASTORE, // - + // -3, //BASTORE, // - + // -3, //CASTORE, // - + // -3, //SASTORE, // - + // -1, //POP, // - + // -2, //POP2, // - + // 1, //DUP, // - + // 1, //DUP_X1, // - + // 1, //DUP_X2, // - + // 2, //DUP2, // - + // 2, //DUP2_X1, // - + // 2, //DUP2_X2, // - + // 0, //SWAP, // - + // -1, //IADD, // - + // -2, //LADD, // - + // -1, //FADD, // - + // -2, //DADD, // - + // -1, //ISUB, // - + // -2, //LSUB, // - + // -1, //FSUB, // - + // -2, //DSUB, // - + // -1, //IMUL, // - + // -2, //LMUL, // - + // -1, //FMUL, // - + // -2, //DMUL, // - + // -1, //IDIV, // - + // -2, //LDIV, // - + // -1, //FDIV, // - + // -2, //DDIV, // - + // -1, //IREM, // - + // -2, //LREM, // - + // -1, //FREM, // - + // -2, //DREM, // - + // 0, //INEG, // - + // 0, //LNEG, // - + // 0, //FNEG, // - + // 0, //DNEG, // - + // -1, //ISHL, // - + // -1, //LSHL, // - + // -1, //ISHR, // - + // -1, //LSHR, // - + // -1, //IUSHR, // - + // -1, //LUSHR, // - + // -1, //IAND, // - + // -2, //LAND, // - + // -1, //IOR, // - + // -2, //LOR, // - + // -1, //IXOR, // - + // -2, //LXOR, // - + // 0, //IINC, // visitIincInsn + // 1, //I2L, // visitInsn + // 0, //I2F, // - + // 1, //I2D, // - + // -1, //L2I, // - + // -1, //L2F, // - + // 0, //L2D, // - + // 0, //F2I, // - + // 1, //F2L, // - + // 1, //F2D, // - + // -1, //D2I, // - + // 0, //D2L, // - + // -1, //D2F, // - + // 0, //I2B, // - + // 0, //I2C, // - + // 0, //I2S, // - + // -3, //LCMP, // - + // -1, //FCMPL, // - + // -1, //FCMPG, // - + // -3, //DCMPL, // - + // -3, //DCMPG, // - + // -1, //IFEQ, // visitJumpInsn + // -1, //IFNE, // - + // -1, //IFLT, // - + // -1, //IFGE, // - + // -1, //IFGT, // - + // -1, //IFLE, // - + // -2, //IF_ICMPEQ, // - + // -2, //IF_ICMPNE, // - + // -2, //IF_ICMPLT, // - + // -2, //IF_ICMPGE, // - + // -2, //IF_ICMPGT, // - + // -2, //IF_ICMPLE, // - + // -2, //IF_ACMPEQ, // - + // -2, //IF_ACMPNE, // - + // 0, //GOTO, // - + // 1, //JSR, // - + // 0, //RET, // visitVarInsn + // -1, //TABLESWITCH, // visiTableSwitchInsn + // -1, //LOOKUPSWITCH, // visitLookupSwitch + // -1, //IRETURN, // visitInsn + // -2, //LRETURN, // - + // -1, //FRETURN, // - + // -2, //DRETURN, // - + // -1, //ARETURN, // - + // 0, //RETURN, // - + // NA, //GETSTATIC, // visitFieldInsn + // NA, //PUTSTATIC, // - + // NA, //GETFIELD, // - + // NA, //PUTFIELD, // - + // NA, //INVOKEVIRTUAL, // visitMethodInsn + // NA, //INVOKESPECIAL, // - + // NA, //INVOKESTATIC, // - + // NA, //INVOKEINTERFACE, // - + // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn + // 1, //NEW, // visitTypeInsn + // 0, //NEWARRAY, // visitIntInsn + // 0, //ANEWARRAY, // visitTypeInsn + // 0, //ARRAYLENGTH, // visitInsn + // NA, //ATHROW, // - + // 0, //CHECKCAST, // visitTypeInsn + // 0, //INSTANCEOF, // - + // -1, //MONITORENTER, // visitInsn + // -1, //MONITOREXIT, // - + // NA, //WIDE, // NOT VISITED + // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn + // -1, //IFNULL, // visitJumpInsn + // -1, //IFNONNULL, // - + // NA, //GOTO_W, // - + // NA, //JSR_W, // - + // }; + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('E' + b[i])); + // } + // System.err.println(); + } + + /** + * The label (i.e. basic block) to which these input and output stack map + * frames correspond. + */ + Label owner; + + /** + * The input stack map frame locals. + */ + int[] inputLocals; + + /** + * The input stack map frame stack. + */ + int[] inputStack; + + /** + * The output stack map frame locals. + */ + private int[] outputLocals; + + /** + * The output stack map frame stack. + */ + private int[] outputStack; + + /** + * Relative size of the output stack. The exact semantics of this field + * depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the size of + * the output stack relatively to the top of the input stack. + * + * When the stack map frames are completely computed, this field is the + * actual number of types in {@link #outputStack}. + */ + private int outputStackTop; + + /** + * Number of types that are initialized in the basic block. + * + * @see #initializations + */ + private int initializationCount; + + /** + * The types that are initialized in the basic block. A constructor + * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace + * <i>every occurence</i> of this type in the local variables and in the + * operand stack. This cannot be done during the first phase of the + * algorithm since, during this phase, the local variables and the operand + * stack are not completely computed. It is therefore necessary to store the + * types on which constructors are invoked in the basic block, in order to + * do this replacement during the second phase of the algorithm, where the + * frames are fully computed. Note that this array can contain types that + * are relative to input locals or to the input stack (see below for the + * description of the algorithm). + */ + private int[] initializations; + + /** + * Returns the output frame local variable type at the given index. + * + * @param local + * the index of the local that must be returned. + * @return the output frame local variable type at the given index. + */ + private int get(final int local) { + if (outputLocals == null || local >= outputLocals.length) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + return LOCAL | local; + } else { + int type = outputLocals[local]; + if (type == 0) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + type = outputLocals[local] = LOCAL | local; + } + return type; + } + } + + /** + * Sets the output frame local variable type at the given index. + * + * @param local + * the index of the local that must be set. + * @param type + * the value of the local that must be set. + */ + private void set(final int local, final int type) { + // creates and/or resizes the output local variables array if necessary + if (outputLocals == null) { + outputLocals = new int[10]; + } + int n = outputLocals.length; + if (local >= n) { + int[] t = new int[Math.max(local + 1, 2 * n)]; + System.arraycopy(outputLocals, 0, t, 0, n); + outputLocals = t; + } + // sets the local variable + outputLocals[local] = type; + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param type + * the type that must be pushed. + */ + private void push(final int type) { + // creates and/or resizes the output stack array if necessary + if (outputStack == null) { + outputStack = new int[10]; + } + int n = outputStack.length; + if (outputStackTop >= n) { + int[] t = new int[Math.max(outputStackTop + 1, 2 * n)]; + System.arraycopy(outputStack, 0, t, 0, n); + outputStack = t; + } + // pushes the type on the output stack + outputStack[outputStackTop++] = type; + // updates the maximun height reached by the output stack, if needed + int top = owner.inputStackTop + outputStackTop; + if (top > owner.outputStackMax) { + owner.outputStackMax = top; + } + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param desc + * the descriptor of the type to be pushed. Can also be a method + * descriptor (in this case this method pushes its return type + * onto the output frame stack). + */ + private void push(final ClassWriter cw, final String desc) { + int type = type(cw, desc); + if (type != 0) { + push(type); + if (type == LONG || type == DOUBLE) { + push(TOP); + } + } + } + + /** + * Returns the int encoding of the given type. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param desc + * a type descriptor. + * @return the int encoding of the given type. + */ + private static int type(final ClassWriter cw, final String desc) { + String t; + int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; + switch (desc.charAt(index)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + // stores the internal name, not the descriptor! + t = desc.substring(index + 1, desc.length() - 1); + return OBJECT | cw.addType(t); + // case '[': + default: + // extracts the dimensions and the element type + int data; + int dims = index + 1; + while (desc.charAt(dims) == '[') { + ++dims; + } + switch (desc.charAt(dims)) { + case 'Z': + data = BOOLEAN; + break; + case 'C': + data = CHAR; + break; + case 'B': + data = BYTE; + break; + case 'S': + data = SHORT; + break; + case 'I': + data = INTEGER; + break; + case 'F': + data = FLOAT; + break; + case 'J': + data = LONG; + break; + case 'D': + data = DOUBLE; + break; + // case 'L': + default: + // stores the internal name, not the descriptor + t = desc.substring(dims + 1, desc.length() - 1); + data = OBJECT | cw.addType(t); + } + return (dims - index) << 28 | data; + } + } + + /** + * Pops a type from the output frame stack and returns its value. + * + * @return the type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // if the output frame stack is empty, pops from the input stack + return STACK | -(--owner.inputStackTop); + } + } + + /** + * Pops the given number of types from the output frame stack. + * + * @param elements + * the number of types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // if the number of elements to be popped is greater than the number + // of elements in the output stack, clear it, and pops the remaining + // elements from the input stack. + owner.inputStackTop -= elements - outputStackTop; + outputStackTop = 0; + } + } + + /** + * Pops a type from the output frame stack. + * + * @param desc + * the descriptor of the type to be popped. Can also be a method + * descriptor (in this case this method pops the types + * corresponding to the method arguments). + */ + private void pop(final String desc) { + char c = desc.charAt(0); + if (c == '(') { + pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1); + } else if (c == 'J' || c == 'D') { + pop(2); + } else { + pop(1); + } + } + + /** + * Adds a new type to the list of types on which a constructor is invoked in + * the basic block. + * + * @param var + * a type on a which a constructor is invoked. + */ + private void init(final int var) { + // creates and/or resizes the initializations array if necessary + if (initializations == null) { + initializations = new int[2]; + } + int n = initializations.length; + if (initializationCount >= n) { + int[] t = new int[Math.max(initializationCount + 1, 2 * n)]; + System.arraycopy(initializations, 0, t, 0, n); + initializations = t; + } + // stores the type to be initialized + initializations[initializationCount++] = var; + } + + /** + * Replaces the given type with the appropriate type if it is one of the + * types on which a constructor is invoked in the basic block. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param t + * a type + * @return t or, if t is one of the types on which a constructor is invoked + * in the basic block, the type corresponding to this constructor. + */ + private int init(final ClassWriter cw, final int t) { + int s; + if (t == UNINITIALIZED_THIS) { + s = OBJECT | cw.addType(cw.thisName); + } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) { + String type = cw.typeTable[t & BASE_VALUE].strVal1; + s = OBJECT | cw.addType(type); + } else { + return t; + } + for (int j = 0; j < initializationCount; ++j) { + int u = initializations[j]; + int dim = u & DIM; + int kind = u & KIND; + if (kind == LOCAL) { + u = dim + inputLocals[u & VALUE]; + } else if (kind == STACK) { + u = dim + inputStack[inputStack.length - (u & VALUE)]; + } + if (t == u) { + return s; + } + } + return t; + } + + /** + * Initializes the input frame of the first basic block from the method + * descriptor. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param access + * the access flags of the method to which this label belongs. + * @param args + * the formal parameter types of this method. + * @param maxLocals + * the maximum number of local variables of this method. + */ + void initInputFrame(final ClassWriter cw, final int access, + final Type[] args, final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int i = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) { + inputLocals[i++] = OBJECT | cw.addType(cw.thisName); + } else { + inputLocals[i++] = UNINITIALIZED_THIS; + } + } + for (int j = 0; j < args.length; ++j) { + int t = type(cw, args[j].getDescriptor()); + inputLocals[i++] = t; + if (t == LONG || t == DOUBLE) { + inputLocals[i++] = TOP; + } + } + while (i < maxLocals) { + inputLocals[i++] = TOP; + } + } + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode + * the opcode of the instruction. + * @param arg + * the operand of the instruction, if any. + * @param cw + * the class writer to which this label belongs. + * @param item + * the operand of the instructions, if any. + */ + void execute(final int opcode, final int arg, final ClassWriter cw, + final Item item) { + int t1, t2, t3, t4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (item.type) { + case ClassWriter.INT: + push(INTEGER); + break; + case ClassWriter.LONG: + push(LONG); + push(TOP); + break; + case ClassWriter.FLOAT: + push(FLOAT); + break; + case ClassWriter.DOUBLE: + push(DOUBLE); + push(TOP); + break; + case ClassWriter.CLASS: + push(OBJECT | cw.addType("java/lang/Class")); + break; + case ClassWriter.STR: + push(OBJECT | cw.addType("java/lang/String")); + break; + case ClassWriter.MTYPE: + push(OBJECT | cw.addType("java/lang/invoke/MethodType")); + break; + // case ClassWriter.HANDLE_BASE + [1..9]: + default: + push(OBJECT | cw.addType("java/lang/invoke/MethodHandle")); + } + break; + case Opcodes.ALOAD: + push(get(arg)); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(INTEGER); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + pop(2); + push(FLOAT); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + t1 = pop(); + push(ELEMENT_OF + t1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + t1 = pop(); + set(arg, t1); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + t1 = pop(); + set(arg, t1); + set(arg + 1, TOP); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + t1 = pop(); + push(t1); + push(t1); + break; + case Opcodes.DUP_X1: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2: + t1 = pop(); + t2 = pop(); + push(t2); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X1: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t2); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + t4 = pop(); + push(t2); + push(t1); + push(t4); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.SWAP: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + break; + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + set(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new RuntimeException( + "JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(cw, item.strVal3); + break; + case Opcodes.PUTSTATIC: + pop(item.strVal3); + break; + case Opcodes.GETFIELD: + pop(1); + push(cw, item.strVal3); + break; + case Opcodes.PUTFIELD: + pop(item.strVal3); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(item.strVal3); + if (opcode != Opcodes.INVOKESTATIC) { + t1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL + && item.strVal2.charAt(0) == '<') { + init(t1); + } + } + push(cw, item.strVal3); + break; + case Opcodes.INVOKEDYNAMIC: + pop(item.strVal2); + push(cw, item.strVal2); + break; + case Opcodes.NEW: + push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + // case Opcodes.T_LONG: + default: + push(ARRAY_OF | LONG); + break; + } + break; + case Opcodes.ANEWARRAY: + String s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, '[' + s); + } else { + push(ARRAY_OF | OBJECT | cw.addType(s)); + } + break; + case Opcodes.CHECKCAST: + s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, s); + } else { + push(OBJECT | cw.addType(s)); + } + break; + // case Opcodes.MULTIANEWARRAY: + default: + pop(arg); + push(cw, item.strVal1); + break; + } + } + + /** + * Merges the input frame of the given basic block with the input and output + * frames of this basic block. Returns <tt>true</tt> if the input frame of + * the given label has been changed by this operation. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param frame + * the basic block whose input frame must be updated. + * @param edge + * the kind of the {@link Edge} between this label and 'label'. + * See {@link Edge#info}. + * @return <tt>true</tt> if the input frame of the given label has been + * changed by this operation. + */ + boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + boolean changed = false; + int i, s, dim, kind, t; + + int nLocal = inputLocals.length; + int nStack = inputStack.length; + if (frame.inputLocals == null) { + frame.inputLocals = new int[nLocal]; + changed = true; + } + + for (i = 0; i < nLocal; ++i) { + if (outputLocals != null && i < outputLocals.length) { + s = outputLocals[i]; + if (s == 0) { + t = inputLocals[i]; + } else { + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 + && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + } + } else { + t = inputLocals[i]; + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputLocals, i); + } + + if (edge > 0) { + for (i = 0; i < nLocal; ++i) { + t = inputLocals[i]; + changed |= merge(cw, t, frame.inputLocals, i); + } + if (frame.inputStack == null) { + frame.inputStack = new int[1]; + changed = true; + } + changed |= merge(cw, edge, frame.inputStack, 0); + return changed; + } + + int nInputStack = inputStack.length + owner.inputStackTop; + if (frame.inputStack == null) { + frame.inputStack = new int[nInputStack + outputStackTop]; + changed = true; + } + + for (i = 0; i < nInputStack; ++i) { + t = inputStack[i]; + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, i); + } + for (i = 0; i < outputStackTop; ++i) { + s = outputStack[i]; + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 + && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, nInputStack + i); + } + return changed; + } + + /** + * Merges the type at the given index in the given type array with the given + * type. Returns <tt>true</tt> if the type array has been modified by this + * operation. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param t + * the type with which the type array element must be merged. + * @param types + * an array of types. + * @param index + * the index of the type that must be merged in 'types'. + * @return <tt>true</tt> if the type array has been modified by this + * operation. + */ + private static boolean merge(final ClassWriter cw, int t, + final int[] types, final int index) { + int u = types[index]; + if (u == t) { + // if the types are equal, merge(u,t)=u, so there is no change + return false; + } + if ((t & ~DIM) == NULL) { + if (u == NULL) { + return false; + } + t = NULL; + } + if (u == 0) { + // if types[index] has never been assigned, merge(u,t)=t + types[index] = t; + return true; + } + int v; + if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) { + // if u is a reference type of any dimension + if (t == NULL) { + // if t is the NULL type, merge(u,t)=u, so there is no change + return false; + } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { + // if t and u have the same dimension and same base kind + if ((u & BASE_KIND) == OBJECT) { + // if t is also a reference type, and if u and t have the + // same dimension merge(u,t) = dim(t) | common parent of the + // element types of u and t + v = (t & DIM) | OBJECT + | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); + } else { + // if u and t are array types, but not with the same element + // type, merge(u,t) = dim(u) - 1 | java/lang/Object + int vdim = ELEMENT_OF + (u & DIM); + v = vdim | OBJECT | cw.addType("java/lang/Object"); + } + } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { + // if t is any other reference or array type, the merged type + // is min(udim, tdim) | java/lang/Object, where udim is the + // array dimension of u, minus 1 if u is an array type with a + // primitive element type (and similarly for tdim). + int tdim = (((t & DIM) == 0 || (t & BASE_KIND) == OBJECT) ? 0 + : ELEMENT_OF) + (t & DIM); + int udim = (((u & DIM) == 0 || (u & BASE_KIND) == OBJECT) ? 0 + : ELEMENT_OF) + (u & DIM); + v = Math.min(tdim, udim) | OBJECT + | cw.addType("java/lang/Object"); + } else { + // if t is any other type, merge(u,t)=TOP + v = TOP; + } + } else if (u == NULL) { + // if u is the NULL type, merge(u,t)=t, + // or TOP if t is not a reference type + v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP; + } else { + // if u is any other type, merge(u,t)=TOP whatever t + v = TOP; + } + if (u != v) { + types[index] = v; + return true; + } + return false; + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Handle.java b/spring-core/src/main/java/org/springframework/asm/Handle.java new file mode 100644 index 00000000..adc6f097 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Handle.java @@ -0,0 +1,170 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.springframework.asm; + +/** + * A reference to a field or a method. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public final class Handle { + + /** + * The kind of field or method designated by this Handle. Should be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + final int tag; + + /** + * The internal name of the class that owns the field or method designated + * by this handle. + */ + final String owner; + + /** + * The name of the field or method designated by this handle. + */ + final String name; + + /** + * The descriptor of the field or method designated by this handle. + */ + final String desc; + + /** + * Constructs a new field or method handle. + * + * @param tag + * the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the class that owns the field or method + * designated by this handle. + * @param name + * the name of the field or method designated by this handle. + * @param desc + * the descriptor of the field or method designated by this + * handle. + */ + public Handle(int tag, String owner, String name, String desc) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.desc = desc; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the class that owns the field or method + * designated by this handle. + * + * @return the internal name of the class that owns the field or method + * designated by this handle. + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return desc; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Handle)) { + return false; + } + Handle h = (Handle) obj; + return tag == h.tag && owner.equals(h.owner) && name.equals(h.name) + && desc.equals(h.desc); + } + + @Override + public int hashCode() { + return tag + owner.hashCode() * name.hashCode() * desc.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual + * representation is: + * + * <pre> + * owner '.' name desc ' ' '(' tag ')' + * </pre> + * + * . As this format is unambiguous, it can be parsed if necessary. + */ + @Override + public String toString() { + return owner + '.' + name + desc + " (" + tag + ')'; + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Handler.java b/spring-core/src/main/java/org/springframework/asm/Handler.java new file mode 100644 index 00000000..50b198e8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Handler.java @@ -0,0 +1,121 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * Information about an exception handler block. + * + * @author Eric Bruneton + */ +class Handler { + + /** + * Beginning of the exception handler's scope (inclusive). + */ + Label start; + + /** + * End of the exception handler's scope (exclusive). + */ + Label end; + + /** + * Beginning of the exception handler's code. + */ + Label handler; + + /** + * Internal name of the type of exceptions handled by this handler, or + * <tt>null</tt> to catch any exceptions. + */ + String desc; + + /** + * Constant pool index of the internal name of the type of exceptions + * handled by this handler, or 0 to catch any exceptions. + */ + int type; + + /** + * Next exception handler block info. + */ + Handler next; + + /** + * Removes the range between start and end from the given exception + * handlers. + * + * @param h + * an exception handler list. + * @param start + * the start of the range to be removed. + * @param end + * the end of the range to be removed. Maybe null. + * @return the exception handler list with the start-end range removed. + */ + static Handler remove(Handler h, Label start, Label end) { + if (h == null) { + return null; + } else { + h.next = remove(h.next, start, end); + } + int hstart = h.start.position; + int hend = h.end.position; + int s = start.position; + int e = end == null ? Integer.MAX_VALUE : end.position; + // if [hstart,hend[ and [s,e[ intervals intersect... + if (s < hend && e > hstart) { + if (s <= hstart) { + if (e >= hend) { + // [hstart,hend[ fully included in [s,e[, h removed + h = h.next; + } else { + // [hstart,hend[ minus [s,e[ = [e,hend[ + h.start = end; + } + } else if (e >= hend) { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + h.end = start; + } else { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[ + Handler g = new Handler(); + g.start = end; + g.end = h.end; + g.handler = h.handler; + g.desc = h.desc; + g.type = h.type; + g.next = h.next; + h.end = start; + h.next = g; + } + } + return h; + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Item.java b/spring-core/src/main/java/org/springframework/asm/Item.java new file mode 100644 index 00000000..91dc701d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Item.java @@ -0,0 +1,314 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A constant pool item. Constant pool items can be created with the 'newXXX' + * methods in the {@link ClassWriter} class. + * + * @author Eric Bruneton + */ +final class Item { + + /** + * Index of this item in the constant pool. + */ + int index; + + /** + * Type of this constant pool item. A single class is used to represent all + * constant pool item types, in order to minimize the bytecode size of this + * package. The value of this field is one of {@link ClassWriter#INT}, + * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT}, + * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8}, + * {@link ClassWriter#STR}, {@link ClassWriter#CLASS}, + * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD}, + * {@link ClassWriter#METH}, {@link ClassWriter#IMETH}, + * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}. + * + * MethodHandle constant 9 variations are stored using a range of 9 values + * from {@link ClassWriter#HANDLE_BASE} + 1 to + * {@link ClassWriter#HANDLE_BASE} + 9. + * + * Special Item types are used for Items that are stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. These special item types are + * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and + * {@link ClassWriter#TYPE_MERGED}. + */ + int type; + + /** + * Value of this item, for an integer item. + */ + int intVal; + + /** + * Value of this item, for a long item. + */ + long longVal; + + /** + * First part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal1; + + /** + * Second part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal2; + + /** + * Third part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal3; + + /** + * The hash code value of this constant pool item. + */ + int hashCode; + + /** + * Link to another constant pool item, used for collision lists in the + * constant pool's hash table. + */ + Item next; + + /** + * Constructs an uninitialized {@link Item}. + */ + Item() { + } + + /** + * Constructs an uninitialized {@link Item} for constant pool element at + * given position. + * + * @param index + * index of the item to be constructed. + */ + Item(final int index) { + this.index = index; + } + + /** + * Constructs a copy of the given item. + * + * @param index + * index of the item to be constructed. + * @param i + * the item that must be copied into the item to be constructed. + */ + Item(final int index, final Item i) { + this.index = index; + type = i.type; + intVal = i.intVal; + longVal = i.longVal; + strVal1 = i.strVal1; + strVal2 = i.strVal2; + strVal3 = i.strVal3; + hashCode = i.hashCode; + } + + /** + * Sets this item to an integer item. + * + * @param intVal + * the value of this item. + */ + void set(final int intVal) { + this.type = ClassWriter.INT; + this.intVal = intVal; + this.hashCode = 0x7FFFFFFF & (type + intVal); + } + + /** + * Sets this item to a long item. + * + * @param longVal + * the value of this item. + */ + void set(final long longVal) { + this.type = ClassWriter.LONG; + this.longVal = longVal; + this.hashCode = 0x7FFFFFFF & (type + (int) longVal); + } + + /** + * Sets this item to a float item. + * + * @param floatVal + * the value of this item. + */ + void set(final float floatVal) { + this.type = ClassWriter.FLOAT; + this.intVal = Float.floatToRawIntBits(floatVal); + this.hashCode = 0x7FFFFFFF & (type + (int) floatVal); + } + + /** + * Sets this item to a double item. + * + * @param doubleVal + * the value of this item. + */ + void set(final double doubleVal) { + this.type = ClassWriter.DOUBLE; + this.longVal = Double.doubleToRawLongBits(doubleVal); + this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal); + } + + /** + * Sets this item to an item that do not hold a primitive value. + * + * @param type + * the type of this item. + * @param strVal1 + * first part of the value of this item. + * @param strVal2 + * second part of the value of this item. + * @param strVal3 + * third part of the value of this item. + */ + void set(final int type, final String strVal1, final String strVal2, + final String strVal3) { + this.type = type; + this.strVal1 = strVal1; + this.strVal2 = strVal2; + this.strVal3 = strVal3; + switch (type) { + case ClassWriter.CLASS: + this.intVal = 0; // intVal of a class must be zero, see visitInnerClass + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); + return; + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); + return; + case ClassWriter.NAME_TYPE: { + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode()); + return; + } + // ClassWriter.FIELD: + // ClassWriter.METH: + // ClassWriter.IMETH: + // ClassWriter.HANDLE_BASE + 1..9 + default: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode() * strVal3.hashCode()); + } + } + + /** + * Sets the item to an InvokeDynamic item. + * + * @param name + * invokedynamic's name. + * @param desc + * invokedynamic's desc. + * @param bsmIndex + * zero based index into the class attribute BootrapMethods. + */ + void set(String name, String desc, int bsmIndex) { + this.type = ClassWriter.INDY; + this.longVal = bsmIndex; + this.strVal1 = name; + this.strVal2 = desc; + this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex + * strVal1.hashCode() * strVal2.hashCode()); + } + + /** + * Sets the item to a BootstrapMethod item. + * + * @param position + * position in byte in the class attribute BootrapMethods. + * @param hashCode + * hashcode of the item. This hashcode is processed from the + * hashcode of the bootstrap method and the hashcode of all + * bootstrap arguments. + */ + void set(int position, int hashCode) { + this.type = ClassWriter.BSM; + this.intVal = position; + this.hashCode = hashCode; + } + + /** + * Indicates if the given item is equal to this one. <i>This method assumes + * that the two items have the same {@link #type}</i>. + * + * @param i + * the item to be compared to this one. Both items must have the + * same {@link #type}. + * @return <tt>true</tt> if the given item if equal to this one, + * <tt>false</tt> otherwise. + */ + boolean isEqualTo(final Item i) { + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + return i.strVal1.equals(strVal1); + case ClassWriter.TYPE_MERGED: + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + return i.longVal == longVal; + case ClassWriter.INT: + case ClassWriter.FLOAT: + return i.intVal == intVal; + case ClassWriter.TYPE_UNINIT: + return i.intVal == intVal && i.strVal1.equals(strVal1); + case ClassWriter.NAME_TYPE: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2); + case ClassWriter.INDY: { + return i.longVal == longVal && i.strVal1.equals(strVal1) + && i.strVal2.equals(strVal2); + } + // case ClassWriter.FIELD: + // case ClassWriter.METH: + // case ClassWriter.IMETH: + // case ClassWriter.HANDLE_BASE + 1..9 + default: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2) + && i.strVal3.equals(strVal3); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/asm/Label.java b/spring-core/src/main/java/org/springframework/asm/Label.java new file mode 100644 index 00000000..deb84a09 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Label.java @@ -0,0 +1,565 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A label represents a position in the bytecode of a method. Labels are used + * for jump, goto, and switch instructions, and for try catch blocks. A label + * designates the <i>instruction</i> that is just after. Note however that there + * can be other elements between a label and the instruction it designates (such + * as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * Indicates if this label is only used for debug attributes. Such a label + * is not the start of a basic block, the target of a jump instruction, or + * an exception handler. It can be safely ignored in control flow graph + * analysis algorithms (for optimization purposes). + */ + static final int DEBUG = 1; + + /** + * Indicates if the position of this label is known. + */ + static final int RESOLVED = 2; + + /** + * Indicates if this label has been updated, after instruction resizing. + */ + static final int RESIZED = 4; + + /** + * Indicates if this basic block has been pushed in the basic block stack. + * See {@link MethodWriter#visitMaxs visitMaxs}. + */ + static final int PUSHED = 8; + + /** + * Indicates if this label is the target of a jump instruction, or the start + * of an exception handler. + */ + static final int TARGET = 16; + + /** + * Indicates if a stack map frame must be stored for this label. + */ + static final int STORE = 32; + + /** + * Indicates if this label corresponds to a reachable basic block. + */ + static final int REACHABLE = 64; + + /** + * Indicates if this basic block ends with a JSR instruction. + */ + static final int JSR = 128; + + /** + * Indicates if this basic block ends with a RET instruction. + */ + static final int RET = 256; + + /** + * Indicates if this basic block is the start of a subroutine. + */ + static final int SUBROUTINE = 512; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(null, ...) call. + */ + static final int VISITED = 1024; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(!null, ...) call. + */ + static final int VISITED2 = 2048; + + /** + * Field used to associate user information to a label. Warning: this field + * is used by the ASM tree package. In order to use it with the ASM tree + * package you must override the + * {@code org.objectweb.asm.tree.MethodNode#getLabelNode} method. + */ + public Object info; + + /** + * Flags that indicate the status of this label. + * + * @see #DEBUG + * @see #RESOLVED + * @see #RESIZED + * @see #PUSHED + * @see #TARGET + * @see #STORE + * @see #REACHABLE + * @see #JSR + * @see #RET + */ + int status; + + /** + * The line number corresponding to this label, if known. If there are + * several lines, each line is stored in a separate label, all linked via + * their next field (these links are created in ClassReader and removed just + * before visitLabel is called, so that this does not impact the rest of the + * code). + */ + int line; + + /** + * The position of this label in the code, if known. + */ + int position; + + /** + * Number of forward references to this label, times two. + */ + private int referenceCount; + + /** + * Informations about forward references. Each forward reference is + * described by two consecutive integers in this array: the first one is the + * position of the first byte of the bytecode instruction that contains the + * forward reference, while the second is the position of the first byte of + * the forward reference itself. In fact the sign of the first integer + * indicates if this reference uses 2 or 4 bytes, and its absolute value + * gives the position of the bytecode instruction. This array is also used + * as a bitset to store the subroutines to which a basic block belongs. This + * information is needed in {@link MethodWriter#visitMaxs}, after all + * forward references have been resolved. Hence the same array can be used + * for both purposes without problems. + */ + private int[] srcAndRefPositions; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow and data flow graph analysis algorithms (used + * to compute the maximum stack size or the stack map frames). A control + * flow graph contains one node per "basic block", and one edge per "jump" + * from one basic block to another. Each node (i.e., each basic block) is + * represented by the Label object that corresponds to the first instruction + * of this basic block. Each node also stores the list of its successors in + * the graph, as a linked list of Edge objects. + * + * The control flow analysis algorithms used to compute the maximum stack + * size or the stack map frames are similar and use two steps. The first + * step, during the visit of each instruction, builds information about the + * state of the local variables and the operand stack at the end of each + * basic block, called the "output frame", <i>relatively</i> to the frame + * state at the beginning of the basic block, which is called the "input + * frame", and which is <i>unknown</i> during this step. The second step, in + * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes + * information about the input frame of each basic block, from the input + * state of the first basic block (known from the method signature), and by + * the using the previously computed relative output frames. + * + * The algorithm used to compute the maximum stack size only computes the + * relative output and absolute input stack heights, while the algorithm + * used to compute stack map frames computes relative output frames and + * absolute input frames. + */ + + /** + * Start of the output stack relatively to the input stack. The exact + * semantics of this field depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the number of + * elements in the input stack. + * + * When the stack map frames are completely computed, this field is the + * offset of the first output stack element relatively to the top of the + * input stack. This offset is always negative or null. A null offset means + * that the output stack must be appended to the input stack. A -n offset + * means that the first n output stack elements must replace the top n input + * stack elements, and that the other elements must be appended to the input + * stack. + */ + int inputStackTop; + + /** + * Maximum height reached by the output stack, relatively to the top of the + * input stack. This maximum is always positive or null. + */ + int outputStackMax; + + /** + * Information about the input and output stack map frames of this basic + * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES} + * option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited. This linked + * list does not include labels used for debug info only. If + * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it + * does not contain successive labels that denote the same bytecode position + * (in this case only the first label appears in this list). + */ + Label successor; + + /** + * The successors of this node in the control flow graph. These successors + * are stored in a linked list of {@link Edge Edge} objects, linked to each + * other by their {@link Edge#next} field. + */ + Edge successors; + + /** + * The next basic block in the basic block stack. This stack is used in the + * main loop of the fix point algorithm used in the second step of the + * control flow analysis algorithms. It is also used in + * {@link #visitSubroutine} to avoid using a recursive method, and in + * ClassReader to temporarily store multiple source lines for a label. + * + * @see MethodWriter#visitMaxs + */ + Label next; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new label. + */ + public Label() { + } + + // ------------------------------------------------------------------------ + // Methods to compute offsets and to manage forward references + // ------------------------------------------------------------------------ + + /** + * Returns the offset corresponding to this label. This offset is computed + * from the start of the method's bytecode. <i>This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters.</i> + * + * @return the offset corresponding to this label. + * @throws IllegalStateException + * if this label is not resolved yet. + */ + public int getOffset() { + if ((status & RESOLVED) == 0) { + throw new IllegalStateException( + "Label offset position has not been resolved yet"); + } + return position; + } + + /** + * Puts a reference to this label in the bytecode of a method. If the + * position of the label is known, the offset is computed and written + * directly. Otherwise, a null offset is written and a new forward reference + * is declared for this label. + * + * @param owner + * the code writer that calls this method. + * @param out + * the bytecode of the method. + * @param source + * the position of first byte of the bytecode instruction that + * contains this label. + * @param wideOffset + * <tt>true</tt> if the reference must be stored in 4 bytes, or + * <tt>false</tt> if it must be stored with 2 bytes. + * @throws IllegalArgumentException + * if this label has not been created by the given code writer. + */ + void put(final MethodWriter owner, final ByteVector out, final int source, + final boolean wideOffset) { + if ((status & RESOLVED) == 0) { + if (wideOffset) { + addReference(-1 - source, out.length); + out.putInt(-1); + } else { + addReference(source, out.length); + out.putShort(-1); + } + } else { + if (wideOffset) { + out.putInt(position - source); + } else { + out.putShort(position - source); + } + } + } + + /** + * Adds a forward reference to this label. This method must be called only + * for a true forward reference, i.e. only if this label is not resolved + * yet. For backward references, the offset of the reference can be, and + * must be, computed and stored directly. + * + * @param sourcePosition + * the position of the referencing instruction. This position + * will be used to compute the offset of this forward reference. + * @param referencePosition + * the position where the offset for this forward reference must + * be stored. + */ + private void addReference(final int sourcePosition, + final int referencePosition) { + if (srcAndRefPositions == null) { + srcAndRefPositions = new int[6]; + } + if (referenceCount >= srcAndRefPositions.length) { + int[] a = new int[srcAndRefPositions.length + 6]; + System.arraycopy(srcAndRefPositions, 0, a, 0, + srcAndRefPositions.length); + srcAndRefPositions = a; + } + srcAndRefPositions[referenceCount++] = sourcePosition; + srcAndRefPositions[referenceCount++] = referencePosition; + } + + /** + * Resolves all forward references to this label. This method must be called + * when this label is added to the bytecode of the method, i.e. when its + * position becomes known. This method fills in the blanks that where left + * in the bytecode by each forward reference previously added to this label. + * + * @param owner + * the code writer that calls this method. + * @param position + * the position of this label in the bytecode. + * @param data + * the bytecode of the method. + * @return <tt>true</tt> if a blank that was left for this label was to + * small to store the offset. In such a case the corresponding jump + * instruction is replaced with a pseudo instruction (using unused + * opcodes) using an unsigned two bytes offset. These pseudo + * instructions will need to be replaced with true instructions with + * wider offsets (4 bytes instead of 2). This is done in + * {@link MethodWriter#resizeInstructions}. + * @throws IllegalArgumentException + * if this label has already been resolved, or if it has not + * been created by the given code writer. + */ + boolean resolve(final MethodWriter owner, final int position, + final byte[] data) { + boolean needUpdate = false; + this.status |= RESOLVED; + this.position = position; + int i = 0; + while (i < referenceCount) { + int source = srcAndRefPositions[i++]; + int reference = srcAndRefPositions[i++]; + int offset; + if (source >= 0) { + offset = position - source; + if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { + /* + * changes the opcode of the jump instruction, in order to + * be able to find it later (see resizeInstructions in + * MethodWriter). These temporary opcodes are similar to + * jump instruction opcodes, except that the 2 bytes offset + * is unsigned (and can therefore represent values from 0 to + * 65535, which is sufficient since the size of a method is + * limited to 65535 bytes). + */ + int opcode = data[reference - 1] & 0xFF; + if (opcode <= Opcodes.JSR) { + // changes IFEQ ... JSR to opcodes 202 to 217 + data[reference - 1] = (byte) (opcode + 49); + } else { + // changes IFNULL and IFNONNULL to opcodes 218 and 219 + data[reference - 1] = (byte) (opcode + 20); + } + needUpdate = true; + } + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } else { + offset = position + source + 1; + data[reference++] = (byte) (offset >>> 24); + data[reference++] = (byte) (offset >>> 16); + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } + } + return needUpdate; + } + + /** + * Returns the first label of the series to which this label belongs. For an + * isolated label or for the first label in a series of successive labels, + * this method returns the label itself. For other labels it returns the + * first label of the series. + * + * @return the first label of the series to which this label belongs. + */ + Label getFirst() { + return !ClassReader.FRAMES || frame == null ? this : frame.owner; + } + + // ------------------------------------------------------------------------ + // Methods related to subroutines + // ------------------------------------------------------------------------ + + /** + * Returns true is this basic block belongs to the given subroutine. + * + * @param id + * a subroutine id. + * @return true is this basic block belongs to the given subroutine. + */ + boolean inSubroutine(final long id) { + if ((status & Label.VISITED) != 0) { + return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0; + } + return false; + } + + /** + * Returns true if this basic block and the given one belong to a common + * subroutine. + * + * @param block + * another basic block. + * @return true if this basic block and the given one belong to a common + * subroutine. + */ + boolean inSameSubroutine(final Label block) { + if ((status & VISITED) == 0 || (block.status & VISITED) == 0) { + return false; + } + for (int i = 0; i < srcAndRefPositions.length; ++i) { + if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) { + return true; + } + } + return false; + } + + /** + * Marks this basic block as belonging to the given subroutine. + * + * @param id + * a subroutine id. + * @param nbSubroutines + * the total number of subroutines in the method. + */ + void addToSubroutine(final long id, final int nbSubroutines) { + if ((status & VISITED) == 0) { + status |= VISITED; + srcAndRefPositions = new int[nbSubroutines / 32 + 1]; + } + srcAndRefPositions[(int) (id >>> 32)] |= (int) id; + } + + /** + * Finds the basic blocks that belong to a given subroutine, and marks these + * blocks as belonging to this subroutine. This method follows the control + * flow graph to find all the blocks that are reachable from the current + * block WITHOUT following any JSR target. + * + * @param JSR + * a JSR block that jumps to this subroutine. If this JSR is not + * null it is added to the successor of the RET blocks found in + * the subroutine. + * @param id + * the id of this subroutine. + * @param nbSubroutines + * the total number of subroutines in the method. + */ + void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) { + // user managed stack of labels, to avoid using a recursive method + // (recursivity can lead to stack overflow with very large methods) + Label stack = this; + while (stack != null) { + // removes a label l from the stack + Label l = stack; + stack = l.next; + l.next = null; + + if (JSR != null) { + if ((l.status & VISITED2) != 0) { + continue; + } + l.status |= VISITED2; + // adds JSR to the successors of l, if it is a RET block + if ((l.status & RET) != 0) { + if (!l.inSameSubroutine(JSR)) { + Edge e = new Edge(); + e.info = l.inputStackTop; + e.successor = JSR.successors.successor; + e.next = l.successors; + l.successors = e; + } + } + } else { + // if the l block already belongs to subroutine 'id', continue + if (l.inSubroutine(id)) { + continue; + } + // marks the l block as belonging to subroutine 'id' + l.addToSubroutine(id, nbSubroutines); + } + // pushes each successor of l on the stack, except JSR targets + Edge e = l.successors; + while (e != null) { + // if the l block is a JSR block, then 'l.successors.next' leads + // to the JSR target (see {@link #visitJumpInsn}) and must + // therefore not be followed + if ((l.status & Label.JSR) == 0 || e != l.successors.next) { + // pushes e.successor on the stack if it not already added + if (e.successor.next == null) { + e.successor.next = stack; + stack = e.successor; + } + } + e = e.next; + } + } + } + + // ------------------------------------------------------------------------ + // Overriden Object methods + // ------------------------------------------------------------------------ + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java new file mode 100644 index 00000000..4d5c50b9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java @@ -0,0 +1,890 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A visitor to visit a Java method. The methods of this class must be called in + * the following order: ( <tt>visitParameter</tt> )* [ + * <tt>visitAnnotationDefault</tt> ] ( <tt>visitAnnotation</tt> | + * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* [ + * <tt>visitCode</tt> ( <tt>visitFrame</tt> | <tt>visit<i>X</i>Insn</tt> | + * <tt>visitLabel</tt> | <tt>visitInsnAnnotation</tt> | + * <tt>visitTryCatchBlock</tt> | <tt>visitTryCatchBlockAnnotation</tt> | + * <tt>visitLocalVariable</tt> | <tt>visitLocalVariableAnnotation</tt> | + * <tt>visitLineNumber</tt> )* <tt>visitMaxs</tt> ] <tt>visitEnd</tt>. In + * addition, the <tt>visit<i>X</i>Insn</tt> and <tt>visitLabel</tt> methods must + * be called in the sequential order of the bytecode instructions of the visited + * code, <tt>visitInsnAnnotation</tt> must be called <i>after</i> the annotated + * instruction, <tt>visitTryCatchBlock</tt> must be called <i>before</i> the + * labels passed as arguments have been visited, + * <tt>visitTryCatchBlockAnnotation</tt> must be called <i>after</i> the + * corresponding try catch block has been visited, and the + * <tt>visitLocalVariable</tt>, <tt>visitLocalVariableAnnotation</tt> and + * <tt>visitLineNumber</tt> methods must be called <i>after</i> the labels + * passed as arguments have been visited. + * + * @author Eric Bruneton + */ +public abstract class MethodVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The method visitor to which this visitor must delegate method calls. May + * be null. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param mv + * the method visitor to which this visitor must delegate method + * calls. May be null. + */ + public MethodVisitor(final int api, final MethodVisitor mv) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.mv = mv; + } + + // ------------------------------------------------------------------------- + // Parameters, annotations and non standard attributes + // ------------------------------------------------------------------------- + + /** + * Visits a parameter of this method. + * + * @param name + * parameter name or null if none is provided. + * @param access + * the parameter's access flags, only <tt>ACC_FINAL</tt>, + * <tt>ACC_SYNTHETIC</tt> or/and <tt>ACC_MANDATED</tt> are + * allowed (see {@link Opcodes}). + */ + public void visitParameter(String name, int access) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (mv != null) { + mv.visitParameter(name, access); + } + } + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this + * annotation interface method, or <tt>null</tt> if this visitor is + * not interested in visiting this default value. The 'name' + * parameters passed to the methods of this annotation visitor are + * ignored. Moreover, exacly one visit method must be called on this + * annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (mv != null) { + return mv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the method signature. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#METHOD_TYPE_PARAMETER + * METHOD_TYPE_PARAMETER}, + * {@link TypeReference#METHOD_TYPE_PARAMETER_BOUND + * METHOD_TYPE_PARAMETER_BOUND}, + * {@link TypeReference#METHOD_RETURN METHOD_RETURN}, + * {@link TypeReference#METHOD_RECEIVER METHOD_RECEIVER}, + * {@link TypeReference#METHOD_FORMAL_PARAMETER + * METHOD_FORMAL_PARAMETER} or {@link TypeReference#THROWS + * THROWS}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (mv != null) { + return mv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter + * the parameter index. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, boolean visible) { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (mv != null) { + mv.visitAttribute(attr); + } + } + + /** + * Starts the visit of the method's code, if any (i.e. non abstract method). + */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack + * elements. This method must(*) be called <i>just before</i> any + * instruction <b>i</b> that follows an unconditional branch instruction + * such as GOTO or THROW, that is the target of a jump instruction, or that + * starts an exception handler block. The visited types must describe the + * values of the local variables and of the operand stack elements <i>just + * before</i> <b>i</b> is executed.<br> + * <br> + * (*) this is mandatory only for classes whose version is greater than or + * equal to {@link Opcodes#V1_6 V1_6}. <br> + * <br> + * The frames of a method must be given either in expanded form, or in + * compressed form (all frames must use the same format, i.e. you must not + * mix expanded and compressed frames within a single method): + * <ul> + * <li>In expanded form, all frames must have the F_NEW type.</li> + * <li>In compressed form, frames are basically "deltas" from the state of + * the previous frame: + * <ul> + * <li>{@link Opcodes#F_SAME} representing frame with exactly the same + * locals as the previous frame and with the empty stack.</li> + * <li>{@link Opcodes#F_SAME1} representing frame with exactly the same + * locals as the previous frame and with single value on the stack ( + * <code>nStack</code> is 1 and <code>stack[0]</code> contains value for the + * type of the stack item).</li> + * <li>{@link Opcodes#F_APPEND} representing frame with current locals are + * the same as the locals in the previous frame, except that additional + * locals are defined (<code>nLocal</code> is 1, 2 or 3 and + * <code>local</code> elements contains values representing added types).</li> + * <li>{@link Opcodes#F_CHOP} representing frame with current locals are the + * same as the locals in the previous frame, except that the last 1-3 locals + * are absent and with the empty stack (<code>nLocals</code> is 1, 2 or 3).</li> + * <li>{@link Opcodes#F_FULL} representing complete frame data.</li> + * </ul> + * </li> + * </ul> + * <br> + * In both cases the first frame, corresponding to the method's parameters + * and access flags, is implicit and must not be visited. Also, it is + * illegal to visit two or more frames for the same code location (i.e., at + * least one instruction must be visited between two calls to visitFrame). + * + * @param type + * the type of this stack map frame. Must be + * {@link Opcodes#F_NEW} for expanded frames, or + * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for + * compressed frames. + * @param nLocal + * the number of local variables in the visited frame. + * @param local + * the local variable types in this frame. This array must not be + * modified. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param nStack + * the number of operand stack elements in the visited frame. + * @param stack + * the operand stack types in this frame. This array must not be + * modified. Its content has the same format as the "local" + * array. + * @throws IllegalStateException + * if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a + * Opcodes#F_SAME frame, in which case it is silently ignored). + */ + public void visitFrame(int type, int nLocal, Object[] local, int nStack, + Object[] stack) { + if (mv != null) { + mv.visitFrame(type, nLocal, local, nStack, stack); + } + } + + // ------------------------------------------------------------------------- + // Normal instructions + // ------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, + * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, + * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, + * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, + * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, + * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, + * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, + * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, + * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, + * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, + * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, + * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, + * or MONITOREXIT. + */ + public void visitInsn(int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either BIPUSH, SIPUSH or NEWARRAY. + * @param operand + * the operand of the instruction to be visited.<br> + * When opcode is BIPUSH, operand value should be between + * Byte.MIN_VALUE and Byte.MAX_VALUE.<br> + * When opcode is SIPUSH, operand value should be between + * Short.MIN_VALUE and Short.MAX_VALUE.<br> + * When opcode is NEWARRAY, operand value should be one of + * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, + * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, + * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, + * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(int opcode, int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an + * instruction that loads or stores the value of a local variable. + * + * @param opcode + * the opcode of the local variable instruction to be visited. + * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, + * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var + * the operand of the instruction to be visited. This operand is + * the index of a local variable. + */ + public void visitVarInsn(int opcode, int var) { + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that + * takes the internal name of a class as parameter. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type + * the operand of the instruction to be visited. This operand + * must be the internal name of an object or array class (see + * {@link Type#getInternalName() getInternalName}). + */ + public void visitTypeInsn(int opcode, String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that + * loads or stores the value of a field of an object. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner + * the internal name of the field's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type Type}). + */ + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, desc); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + */ + @Deprecated + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (api >= Opcodes.ASM5) { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param itf + * if the method's owner class is an interface. + */ + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (api < Opcodes.ASM5) { + if (itf != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new IllegalArgumentException( + "INVOKESPECIAL/STATIC on interfaces require ASM 5"); + } + visitMethodInsn(opcode, owner, name, desc); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double}, {@link String}, {@link Type} or {@link Handle} + * value. This method is allowed to modify the content of the + * array so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + if (mv != null) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may + * jump to another instruction. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, + * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label + * the operand of the instruction to be visited. This operand is + * a label that designates the instruction to which the jump + * instruction may jump. + */ + public void visitJumpInsn(int opcode, Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited + * just after it. + * + * @param label + * a {@link Label Label} object. + */ + public void visitLabel(Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ------------------------------------------------------------------------- + // Special instructions + // ------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in + * future versions of the Java Virtual Machine. To easily detect new + * constant types, implementations of this method should check for + * unexpected constant types, like this: + * + * <pre> + * if (cst instanceof Integer) { + * // ... + * } else if (cst instanceof Float) { + * // ... + * } else if (cst instanceof Long) { + * // ... + * } else if (cst instanceof Double) { + * // ... + * } else if (cst instanceof String) { + * // ... + * } else if (cst instanceof Type) { + * int sort = ((Type) cst).getSort(); + * if (sort == Type.OBJECT) { + * // ... + * } else if (sort == Type.ARRAY) { + * // ... + * } else if (sort == Type.METHOD) { + * // ... + * } else { + * // throw an exception + * } + * } else if (cst instanceof Handle) { + * // ... + * } else { + * // throw an exception + * } + * </pre> + * + * @param cst + * the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double}, a {@link String}, a {@link Type} of OBJECT or + * ARRAY sort for <tt>.class</tt> constants, for classes whose + * version is 49.0, a {@link Type} of METHOD sort or a + * {@link Handle} for MethodType and MethodHandle constants, for + * classes whose version is 51.0. + */ + public void visitLdcInsn(Object cst) { + if (mv != null) { + mv.visitLdcInsn(cst); + } + } + + /** + * Visits an IINC instruction. + * + * @param var + * index of the local variable to be incremented. + * @param increment + * amount to increment the local variable by. + */ + public void visitIincInsn(int var, int increment) { + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min + * the minimum key value. + * @param max + * the maximum key value. + * @param dflt + * beginning of the default handler block. + * @param labels + * beginnings of the handler blocks. <tt>labels[i]</tt> is the + * beginning of the handler block for the <tt>min + i</tt> key. + */ + public void visitTableSwitchInsn(int min, int max, Label dflt, + Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt + * beginning of the default handler block. + * @param keys + * the values of the keys. + * @param labels + * beginnings of the handler blocks. <tt>labels[i]</tt> is the + * beginning of the handler block for the <tt>keys[i]</tt> key. + */ + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param desc + * an array type descriptor (see {@link Type Type}). + * @param dims + * number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(String desc, int dims) { + if (mv != null) { + mv.visitMultiANewArrayInsn(desc, dims); + } + } + + /** + * Visits an annotation on an instruction. This method must be called just + * <i>after</i> the annotated instruction. It can be called several times + * for the same instruction. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#INSTANCEOF INSTANCEOF}, + * {@link TypeReference#NEW NEW}, + * {@link TypeReference#CONSTRUCTOR_REFERENCE + * CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE + * METHOD_REFERENCE}, {@link TypeReference#CAST CAST}, + * {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (mv != null) { + return mv.visitInsnAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + // ------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start + * beginning of the exception handler's scope (inclusive). + * @param end + * end of the exception handler's scope (exclusive). + * @param handler + * beginning of the exception handler's code. + * @param type + * internal name of the type of exceptions handled by the + * handler, or <tt>null</tt> to catch any exceptions (for + * "finally" blocks). + * @throws IllegalArgumentException + * if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel visitLabel} method). + */ + public void visitTryCatchBlock(Label start, Label end, Label handler, + String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits an annotation on an exception handler type. This method must be + * called <i>after</i> the {@link #visitTryCatchBlock} for the annotated + * exception handler. It can be called several times for the same exception + * handler. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#EXCEPTION_PARAMETER + * EXCEPTION_PARAMETER}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (mv != null) { + return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits a local variable declaration. + * + * @param name + * the name of a local variable. + * @param desc + * the type descriptor of this local variable. + * @param signature + * the type signature of this local variable. May be + * <tt>null</tt> if the local variable type does not use generic + * types. + * @param start + * the first instruction corresponding to the scope of this local + * variable (inclusive). + * @param end + * the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index + * the local variable's index. + * @throws IllegalArgumentException + * if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). + */ + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + if (mv != null) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + /** + * Visits an annotation on a local variable type. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#LOCAL_VARIABLE + * LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE + * RESOURCE_VARIABLE}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * <tt>null</tt> if the annotation targets 'typeRef' as a whole. + * @param start + * the fist instructions corresponding to the continuous ranges + * that make the scope of this local variable (inclusive). + * @param end + * the last instructions corresponding to the continuous ranges + * that make the scope of this local variable (exclusive). This + * array must have the same size as the 'start' array. + * @param index + * the local variable's index in each range. This array must have + * the same size as the 'start' array. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * <tt>true</tt> if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or <tt>null</tt> if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1 + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + */ + if (mv != null) { + return mv.visitLocalVariableAnnotation(typeRef, typePath, start, + end, index, desc, visible); + } + return null; + } + + /** + * Visits a line number declaration. + * + * @param line + * a line number. This number refers to the source file from + * which the class was compiled. + * @param start + * the first instruction corresponding to this line number. + * @throws IllegalArgumentException + * if <tt>start</tt> has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). + */ + public void visitLineNumber(int line, Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables + * of the method. + * + * @param maxStack + * maximum stack size of the method. + * @param maxLocals + * maximum number of local variables for the method. + */ + public void visitMaxs(int maxStack, int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java new file mode 100644 index 00000000..0ba89282 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java @@ -0,0 +1,2913 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * A {@link MethodVisitor} that generates methods in bytecode form. Each visit + * method of this class appends the bytecode corresponding to the visited + * instruction to a byte vector, in the order these methods are called. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +class MethodWriter extends MethodVisitor { + + /** + * Pseudo access flag used to denote constructors. + */ + static final int ACC_CONSTRUCTOR = 0x80000; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. + */ + static final int SAME_FRAME = 0; // to 63 (0-3f) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1 + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f) + + /** + * Reserved for future use + */ + static final int RESERVED = 128; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1. Offset is bigger then 63; + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that the k last locals are absent. The value of k is given + * by the formula 251-frame_type. + */ + static final int CHOP_FRAME = 248; // to 250 (f8-fA) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. Offset is bigger then 63; + */ + static final int SAME_FRAME_EXTENDED = 251; // fb + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that k additional locals are defined. The value of k is + * given by the formula frame_type-251. + */ + static final int APPEND_FRAME = 252; // to 254 // fc-fe + + /** + * Full frame + */ + static final int FULL_FRAME = 255; // ff + + /** + * Indicates that the stack map frames must be recomputed from scratch. In + * this case the maximum stack size and number of local variables is also + * recomputed from scratch. + * + * @see #compute + */ + private static final int FRAMES = 0; + + /** + * Indicates that the maximum stack size and number of local variables must + * be automatically computed. + * + * @see #compute + */ + private static final int MAXS = 1; + + /** + * Indicates that nothing must be automatically computed. + * + * @see #compute + */ + private static final int NOTHING = 2; + + /** + * The class writer to which this method must be added. + */ + final ClassWriter cw; + + /** + * Access flags of this method. + */ + private int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * method. + */ + private final int desc; + + /** + * The descriptor of this method. + */ + private final String descriptor; + + /** + * The signature of this method. + */ + String signature; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in <code>cw.cr</code>. More + * precisely, this field gives the index of the first byte to copied from + * <code>cw.cr.b</code>. + */ + int classReaderOffset; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in <code>cw.cr</code>. More + * precisely, this field gives the number of bytes to copied from + * <code>cw.cr.b</code>. + */ + int classReaderLength; + + /** + * Number of exceptions that can be thrown by this method. + */ + int exceptionCount; + + /** + * The exceptions that can be thrown by this method. More precisely, this + * array contains the indexes of the constant pool items that contain the + * internal names of these exception classes. + */ + int[] exceptions; + + /** + * The annotation default attribute of this method. May be <tt>null</tt>. + */ + private ByteVector annd; + + /** + * The runtime visible annotations of this method. May be <tt>null</tt>. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this method. May be <tt>null</tt>. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible type annotations of this method. May be <tt>null</tt> + * . + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this method. May be + * <tt>null</tt>. + */ + private AnnotationWriter itanns; + + /** + * The runtime visible parameter annotations of this method. May be + * <tt>null</tt>. + */ + private AnnotationWriter[] panns; + + /** + * The runtime invisible parameter annotations of this method. May be + * <tt>null</tt>. + */ + private AnnotationWriter[] ipanns; + + /** + * The number of synthetic parameters of this method. + */ + private int synthetics; + + /** + * The non standard attributes of the method. + */ + private Attribute attrs; + + /** + * The bytecode of this method. + */ + private ByteVector code = new ByteVector(); + + /** + * Maximum stack size of this method. + */ + private int maxStack; + + /** + * Maximum number of local variables for this method. + */ + private int maxLocals; + + /** + * Number of local variables in the current stack map frame. + */ + private int currentLocals; + + /** + * Number of stack map frames in the StackMapTable attribute. + */ + private int frameCount; + + /** + * The StackMapTable attribute. + */ + private ByteVector stackMap; + + /** + * The offset of the last frame that was written in the StackMapTable + * attribute. + */ + private int previousFrameOffset; + + /** + * The last frame that was written in the StackMapTable attribute. + * + * @see #frame + */ + private int[] previousFrame; + + /** + * The current stack map frame. The first element contains the offset of the + * instruction to which the frame corresponds, the second element is the + * number of locals and the third one is the number of stack elements. The + * local variables start at index 3 and are followed by the operand stack + * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = + * nStack, frame[3] = nLocal. All types are encoded as integers, with the + * same format as the one used in {@link Label}, but limited to BASE types. + */ + private int[] frame; + + /** + * Number of elements in the exception handler list. + */ + private int handlerCount; + + /** + * The first element in the exception handler list. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list. + */ + private Handler lastHandler; + + /** + * Number of entries in the MethodParameters attribute. + */ + private int methodParametersCount; + + /** + * The MethodParameters attribute. + */ + private ByteVector methodParameters; + + /** + * Number of entries in the LocalVariableTable attribute. + */ + private int localVarCount; + + /** + * The LocalVariableTable attribute. + */ + private ByteVector localVar; + + /** + * Number of entries in the LocalVariableTypeTable attribute. + */ + private int localVarTypeCount; + + /** + * The LocalVariableTypeTable attribute. + */ + private ByteVector localVarType; + + /** + * Number of entries in the LineNumberTable attribute. + */ + private int lineNumberCount; + + /** + * The LineNumberTable attribute. + */ + private ByteVector lineNumber; + + /** + * The start offset of the last visited instruction. + */ + private int lastCodeOffset; + + /** + * The runtime visible type annotations of the code. May be <tt>null</tt>. + */ + private AnnotationWriter ctanns; + + /** + * The runtime invisible type annotations of the code. May be <tt>null</tt>. + */ + private AnnotationWriter ictanns; + + /** + * The non standard attributes of the method's code. + */ + private Attribute cattrs; + + /** + * Indicates if some jump instructions are too small and need to be resized. + */ + private boolean resize; + + /** + * The number of subroutines in this method. + */ + private int subroutines; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow graph analysis algorithm (used to compute the + * maximum stack size). A control flow graph contains one node per "basic + * block", and one edge per "jump" from one basic block to another. Each + * node (i.e., each basic block) is represented by the Label object that + * corresponds to the first instruction of this basic block. Each node also + * stores the list of its successors in the graph, as a linked list of Edge + * objects. + */ + + /** + * Indicates what must be automatically computed. + * + * @see #FRAMES + * @see #MAXS + * @see #NOTHING + */ + private final int compute; + + /** + * A list of labels. This list is the list of basic blocks in the method, + * i.e. a list of Label objects linked to each other by their + * {@link Label#successor} field, in the order they are visited by + * {@link MethodVisitor#visitLabel}, and starting with the first basic + * block. + */ + private Label labels; + + /** + * The previous basic block. + */ + private Label previousBlock; + + /** + * The current basic block. + */ + private Label currentBlock; + + /** + * The (relative) stack size after the last visited instruction. This size + * is relative to the beginning of the current basic block, i.e., the true + * stack size after the last visited instruction is equal to the + * {@link Label#inputStackTop beginStackSize} of the current basic block + * plus <tt>stackSize</tt>. + */ + private int stackSize; + + /** + * The (relative) maximum stack size after the last visited instruction. + * This size is relative to the beginning of the current basic block, i.e., + * the true maximum stack size after the last visited instruction is equal + * to the {@link Label#inputStackTop beginStackSize} of the current basic + * block plus <tt>stackSize</tt>. + */ + private int maxStackSize; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link MethodWriter}. + * + * @param cw + * the class writer in which the method must be added. + * @param access + * the method's access flags (see {@link Opcodes}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type}). + * @param signature + * the method's signature. May be <tt>null</tt>. + * @param exceptions + * the internal names of the method's exceptions. May be + * <tt>null</tt>. + * @param computeMaxs + * <tt>true</tt> if the maximum stack size and number of local + * variables must be automatically computed. + * @param computeFrames + * <tt>true</tt> if the stack map tables must be recomputed from + * scratch. + */ + MethodWriter(final ClassWriter cw, final int access, final String name, + final String desc, final String signature, + final String[] exceptions, final boolean computeMaxs, + final boolean computeFrames) { + super(Opcodes.ASM5); + if (cw.firstMethod == null) { + cw.firstMethod = this; + } else { + cw.lastMethod.mv = this; + } + cw.lastMethod = this; + this.cw = cw; + this.access = access; + if ("<init>".equals(name)) { + this.access |= ACC_CONSTRUCTOR; + } + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + this.descriptor = desc; + if (ClassReader.SIGNATURES) { + this.signature = signature; + } + if (exceptions != null && exceptions.length > 0) { + exceptionCount = exceptions.length; + this.exceptions = new int[exceptionCount]; + for (int i = 0; i < exceptionCount; ++i) { + this.exceptions[i] = cw.newClass(exceptions[i]); + } + } + this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); + if (computeMaxs || computeFrames) { + // updates maxLocals + int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --size; + } + maxLocals = size; + currentLocals = size; + // creates and visits the label for the first basic block + labels = new Label(); + labels.status |= Label.PUSHED; + visitLabel(labels); + } + } + + // ------------------------------------------------------------------------ + // Implementation of the MethodVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visitParameter(String name, int access) { + if (methodParameters == null) { + methodParameters = new ByteVector(); + } + ++methodParametersCount; + methodParameters.putShort((name == null) ? 0 : cw.newUTF8(name)) + .putShort(access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + if (!ClassReader.ANNOTATIONS) { + return null; + } + annd = new ByteVector(); + return new AnnotationWriter(cw, false, annd, null, 0); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, + final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + if ("Ljava/lang/Synthetic;".equals(desc)) { + // workaround for a bug in javac with synthetic parameters + // see ClassReader.readParameterAnnotations + synthetics = Math.max(synthetics, parameter + 1); + return new AnnotationWriter(cw, false, bv, null, 0); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + if (panns == null) { + panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = panns[parameter]; + panns[parameter] = aw; + } else { + if (ipanns == null) { + ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = ipanns[parameter]; + ipanns[parameter] = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attr.isCodeAttribute()) { + attr.next = cattrs; + cattrs = attr; + } else { + attr.next = attrs; + attrs = attr; + } + } + + @Override + public void visitCode() { + } + + @Override + public void visitFrame(final int type, final int nLocal, + final Object[] local, final int nStack, final Object[] stack) { + if (!ClassReader.FRAMES || compute == FRAMES) { + return; + } + + if (type == Opcodes.F_NEW) { + if (previousFrame == null) { + visitImplicitFirstFrame(); + } + currentLocals = nLocal; + int frameIndex = startFrame(code.length, nLocal, nStack); + for (int i = 0; i < nLocal; ++i) { + if (local[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) local[i]); + } else if (local[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) local[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) local[i]).position); + } + } + for (int i = 0; i < nStack; ++i) { + if (stack[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) stack[i]); + } else if (stack[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) stack[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) stack[i]).position); + } + } + endFrame(); + } else { + int delta; + if (stackMap == null) { + stackMap = new ByteVector(); + delta = code.length; + } else { + delta = code.length - previousFrameOffset - 1; + if (delta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = nLocal; + stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + stackMap.putShort(nStack); + for (int i = 0; i < nStack; ++i) { + writeFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta); + break; + case Opcodes.F_SAME: + if (delta < 64) { + stackMap.putByte(delta); + } else { + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + } + break; + case Opcodes.F_SAME1: + if (delta < 64) { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + } else { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + } + writeFrameType(stack[0]); + break; + } + + previousFrameOffset = code.length; + ++frameCount; + } + + maxStack = Math.max(maxStack, nStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + lastCodeOffset = code.length; + // adds the instruction to the bytecode of the method + code.putByte(opcode); + // update currentBlock + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + } else { + // updates current and max stack sizes + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + // if opcode == ATHROW or xRETURN, ends current block (no successor) + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) + || opcode == Opcodes.ATHROW) { + noSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + lastCodeOffset = code.length; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // updates current and max stack sizes only for NEWARRAY + // (stack size variation = 0 for BIPUSH or SIPUSH) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + lastCodeOffset = code.length; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, var, null, null); + } else { + // updates current and max stack sizes + if (opcode == Opcodes.RET) { + // no stack change, but end of current block (no successor) + currentBlock.status |= Label.RET; + // save 'stackSize' here for future use + // (see {@link #findSubroutineSuccessors}) + currentBlock.inputStackTop = stackSize; + noSuccessor(); + } else { // xLOAD or xSTORE + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + } + if (compute != NOTHING) { + // updates max locals + int n; + if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { + n = var + 2; + } else { + n = var + 1; + } + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if (var < 4 && opcode != Opcodes.RET) { + int opt; + if (opcode < Opcodes.ISTORE) { + /* ILOAD_0 */ + opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var; + } else { + /* ISTORE_0 */ + opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var; + } + code.putByte(opt); + } else if (var >= 256) { + code.putByte(196 /* WIDE */).put12(opcode, var); + } else { + code.put11(opcode, var); + } + if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) { + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + lastCodeOffset = code.length; + Item i = cw.newClassItem(type); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, code.length, cw, i); + } else if (opcode == Opcodes.NEW) { + // updates current and max stack sizes only if opcode == NEW + // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, + final String name, final String desc) { + lastCodeOffset = code.length; + Item i = cw.newFieldItem(owner, name, desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + int size; + // computes the stack size variation + char c = desc.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = stackSize + (c == 'D' || c == 'J' ? 1 : 0); + break; + // case Constants.PUTFIELD: + default: + size = stackSize + (c == 'D' || c == 'J' ? -3 : -2); + break; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + lastCodeOffset = code.length; + Item i = cw.newMethodItem(owner, name, desc, itf); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + } else { + size = stackSize - (argSize >> 2) + (argSize & 0x03); + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.INVOKEINTERFACE) { + if (argSize == 0) { + argSize = Type.getArgumentsAndReturnSizes(desc); + i.intVal = argSize; + } + code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); + } else { + code.put12(opcode, i.index); + } + } + + @Override + public void visitInvokeDynamicInsn(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + lastCodeOffset = code.length; + Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.INVOKEDYNAMIC, i.index); + code.putShort(0); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + lastCodeOffset = code.length; + Label nextInsn = null; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + // 'label' is the target of a jump instruction + label.getFirst().status |= Label.TARGET; + // adds 'label' as a successor of this basic block + addSuccessor(Edge.NORMAL, label); + if (opcode != Opcodes.GOTO) { + // creates a Label for the next basic block + nextInsn = new Label(); + } + } else { + if (opcode == Opcodes.JSR) { + if ((label.status & Label.SUBROUTINE) == 0) { + label.status |= Label.SUBROUTINE; + ++subroutines; + } + currentBlock.status |= Label.JSR; + addSuccessor(stackSize + 1, label); + // creates a Label for the next basic block + nextInsn = new Label(); + /* + * note that, by construction in this method, a JSR block + * has at least two successors in the control flow graph: + * the first one leads the next instruction after the JSR, + * while the second one leads to the JSR target. + */ + } else { + // updates current stack size (max stack size unchanged + // because stack size variation always negative in this + // case) + stackSize += Frame.SIZE[opcode]; + addSuccessor(stackSize, label); + } + } + } + // adds the instruction to the bytecode of the method + if ((label.status & Label.RESOLVED) != 0 + && label.position - code.length < Short.MIN_VALUE) { + /* + * case of a backward jump with an offset < -32768. In this case we + * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx + * <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is the + * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where <l'> + * designates the instruction just after the GOTO_W. + */ + if (opcode == Opcodes.GOTO) { + code.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + code.putByte(201); // JSR_W + } else { + // if the IF instruction is transformed into IFNOT GOTO_W the + // next instruction becomes the target of the IFNOT instruction + if (nextInsn != null) { + nextInsn.status |= Label.TARGET; + } + code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + code.putShort(8); // jump offset + code.putByte(200); // GOTO_W + } + label.put(this, code, code.length - 1, true); + } else { + /* + * case of a backward jump with an offset >= -32768, or of a forward + * jump with, of course, an unknown offset. In these cases we store + * the offset in 2 bytes (which will be increased in + * resizeInstructions, if needed). + */ + code.putByte(opcode); + label.put(this, code, code.length - 1, false); + } + if (currentBlock != null) { + if (nextInsn != null) { + // if the jump instruction is not a GOTO, the next instruction + // is also a successor of this instruction. Calling visitLabel + // adds the label of this next instruction as a successor of the + // current block, and starts a new basic block + visitLabel(nextInsn); + } + if (opcode == Opcodes.GOTO) { + noSuccessor(); + } + } + } + + @Override + public void visitLabel(final Label label) { + // resolves previous forward references to label, if any + resize |= label.resolve(this, code.length, code.data); + // updates currentBlock + if ((label.status & Label.DEBUG) != 0) { + return; + } + if (compute == FRAMES) { + if (currentBlock != null) { + if (label.position == currentBlock.position) { + // successive labels, do not start a new basic block + currentBlock.status |= (label.status & Label.TARGET); + label.frame = currentBlock.frame; + return; + } + // ends current block (with one new successor) + addSuccessor(Edge.NORMAL, label); + } + // begins a new current block + currentBlock = label; + if (label.frame == null) { + label.frame = new Frame(); + label.frame.owner = label; + } + // updates the basic block list + if (previousBlock != null) { + if (label.position == previousBlock.position) { + previousBlock.status |= (label.status & Label.TARGET); + label.frame = previousBlock.frame; + currentBlock = previousBlock; + return; + } + previousBlock.successor = label; + } + previousBlock = label; + } else if (compute == MAXS) { + if (currentBlock != null) { + // ends current block (with one new successor) + currentBlock.outputStackMax = maxStackSize; + addSuccessor(stackSize, label); + } + // begins a new current block + currentBlock = label; + // resets the relative current and max stack sizes + stackSize = 0; + maxStackSize = 0; + // updates the basic block list + if (previousBlock != null) { + previousBlock.successor = label; + } + previousBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object cst) { + lastCodeOffset = code.length; + Item i = cw.newConstItem(cst); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); + } else { + int size; + // computes the stack size variation + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + size = stackSize + 2; + } else { + size = stackSize + 1; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + int index = i.index; + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + code.put12(20 /* LDC2_W */, index); + } else if (index >= 256) { + code.put12(19 /* LDC_W */, index); + } else { + code.put11(Opcodes.LDC, index); + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + lastCodeOffset = code.length; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.IINC, var, null, null); + } + } + if (compute != NOTHING) { + // updates max locals + int n = var + 1; + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if ((var > 255) || (increment > 127) || (increment < -128)) { + code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var) + .putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, + final Label dflt, final Label... labels) { + lastCodeOffset = code.length; + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.TABLESWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(min).putInt(max); + for (int i = 0; i < labels.length; ++i) { + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, + final Label[] labels) { + lastCodeOffset = code.length; + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.LOOKUPSWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // adds current block successors + addSuccessor(Edge.NORMAL, dflt); + dflt.getFirst().status |= Label.TARGET; + for (int i = 0; i < labels.length; ++i) { + addSuccessor(Edge.NORMAL, labels[i]); + labels[i].getFirst().status |= Label.TARGET; + } + } else { + // updates current stack size (max stack size unchanged) + --stackSize; + // adds current block successors + addSuccessor(stackSize, dflt); + for (int i = 0; i < labels.length; ++i) { + addSuccessor(stackSize, labels[i]); + } + } + // ends current block + noSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + lastCodeOffset = code.length; + Item i = cw.newClassItem(desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); + } else { + // updates current stack size (max stack size unchanged because + // stack size variation always negative or null) + stackSize += 1 - dims; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + typeRef = (typeRef & 0xFF0000FF) | (lastCodeOffset << 8); + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, + final Label handler, final String type) { + ++handlerCount; + Handler h = new Handler(); + h.start = start; + h.end = end; + h.handler = handler; + h.desc = type; + h.type = type != null ? cw.newClass(type) : 0; + if (lastHandler == null) { + firstHandler = h; + } else { + lastHandler.next = h; + } + lastHandler = h; + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + + @Override + public void visitLocalVariable(final String name, final String desc, + final String signature, final Label start, final Label end, + final int index) { + if (signature != null) { + if (localVarType == null) { + localVarType = new ByteVector(); + } + ++localVarTypeCount; + localVarType.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature)) + .putShort(index); + } + if (localVar == null) { + localVar = new ByteVector(); + } + ++localVarCount; + localVar.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc)) + .putShort(index); + if (compute != NOTHING) { + // updates max locals + char c = desc.charAt(0); + int n = index + (c == 'J' || c == 'D' ? 2 : 1); + if (n > maxLocals) { + maxLocals = n; + } + } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + bv.putByte(typeRef >>> 24).putShort(start.length); + for (int i = 0; i < start.length; ++i) { + bv.putShort(start[i].position) + .putShort(end[i].position - start[i].position) + .putShort(index[i]); + } + if (typePath == null) { + bv.putByte(0); + } else { + int length = typePath.b[typePath.offset] * 2 + 1; + bv.putByteArray(typePath.b, typePath.offset, length); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumber == null) { + lineNumber = new ByteVector(); + } + ++lineNumberCount; + lineNumber.putShort(start.position); + lineNumber.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (resize) { + // replaces the temporary jump opcodes introduced by Label.resolve. + if (ClassReader.RESIZE) { + resizeInstructions(); + } else { + throw new RuntimeException("Method code too large!"); + } + } + if (ClassReader.FRAMES && compute == FRAMES) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start.getFirst(); + Label h = handler.handler.getFirst(); + Label e = handler.end.getFirst(); + // computes the kind of the edges to 'h' + String t = handler.desc == null ? "java/lang/Throwable" + : handler.desc; + int kind = Frame.OBJECT | cw.addType(t); + // h is an exception handler + h.status |= Label.TARGET; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = kind; + b.successor = h; + // adds it to the successors of 'l' + b.next = l.successors; + l.successors = b; + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + // creates and visits the first (implicit) frame + Frame f = labels.frame; + Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, this.maxLocals); + visitFrame(f); + + /* + * fix point algorithm: mark the first basic block as 'changed' + * (i.e. put it in the 'changed' list) and, while there are changed + * basic blocks, choose one, mark it as unchanged, and update its + * successors (which can be changed in the process). + */ + int max = 0; + Label changed = labels; + while (changed != null) { + // removes a basic block from the list of changed basic blocks + Label l = changed; + changed = changed.next; + l.next = null; + f = l.frame; + // a reachable jump target must be stored in the stack map + if ((l.status & Label.TARGET) != 0) { + l.status |= Label.STORE; + } + // all visited labels are reachable, by definition + l.status |= Label.REACHABLE; + // updates the (absolute) maximum stack size + int blockMax = f.inputStack.length + l.outputStackMax; + if (blockMax > max) { + max = blockMax; + } + // updates the successors of the current basic block + Edge e = l.successors; + while (e != null) { + Label n = e.successor.getFirst(); + boolean change = f.merge(cw, n.frame, e.info); + if (change && n.next == null) { + // if n has changed and is not already in the 'changed' + // list, adds it to this list + n.next = changed; + changed = n; + } + e = e.next; + } + } + + // visits all the frames that must be stored in the stack map + Label l = labels; + while (l != null) { + f = l.frame; + if ((l.status & Label.STORE) != 0) { + visitFrame(f); + } + if ((l.status & Label.REACHABLE) == 0) { + // finds start and end of dead basic block + Label k = l.successor; + int start = l.position; + int end = (k == null ? code.length : k.position) - 1; + // if non empty basic block + if (end >= start) { + max = Math.max(max, 1); + // replaces instructions with NOP ... NOP ATHROW + for (int i = start; i < end; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[end] = (byte) Opcodes.ATHROW; + // emits a frame for this unreachable block + int frameIndex = startFrame(start, 0, 1); + frame[frameIndex] = Frame.OBJECT + | cw.addType("java/lang/Throwable"); + endFrame(); + // removes the start-end range from the exception + // handlers + firstHandler = Handler.remove(firstHandler, l, k); + } + } + l = l.successor; + } + + handler = firstHandler; + handlerCount = 0; + while (handler != null) { + handlerCount += 1; + handler = handler.next; + } + + this.maxStack = max; + } else if (compute == MAXS) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start; + Label h = handler.handler; + Label e = handler.end; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = Edge.EXCEPTION; + b.successor = h; + // adds it to the successors of 'l' + if ((l.status & Label.JSR) == 0) { + b.next = l.successors; + l.successors = b; + } else { + // if l is a JSR block, adds b after the first two edges + // to preserve the hypothesis about JSR block successors + // order (see {@link #visitJumpInsn}) + b.next = l.successors.next.next; + l.successors.next.next = b; + } + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + if (subroutines > 0) { + // completes the control flow graph with the RET successors + /* + * first step: finds the subroutines. This step determines, for + * each basic block, to which subroutine(s) it belongs. + */ + // finds the basic blocks that belong to the "main" subroutine + int id = 0; + labels.visitSubroutine(null, 1, subroutines); + // finds the basic blocks that belong to the real subroutines + Label l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + // if this subroutine has not been visited yet... + if ((subroutine.status & Label.VISITED) == 0) { + // ...assigns it a new id and finds its basic blocks + id += 1; + subroutine.visitSubroutine(null, (id / 32L) << 32 + | (1L << (id % 32)), subroutines); + } + } + l = l.successor; + } + // second step: finds the successors of RET blocks + l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + Label L = labels; + while (L != null) { + L.status &= ~Label.VISITED2; + L = L.successor; + } + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + subroutine.visitSubroutine(l, 0, subroutines); + } + l = l.successor; + } + } + + /* + * control flow analysis algorithm: while the block stack is not + * empty, pop a block from this stack, update the max stack size, + * compute the true (non relative) begin stack size of the + * successors of this block, and push these successors onto the + * stack (unless they have already been pushed onto the stack). + * Note: by hypothesis, the {@link Label#inputStackTop} of the + * blocks in the block stack are the true (non relative) beginning + * stack sizes of these blocks. + */ + int max = 0; + Label stack = labels; + while (stack != null) { + // pops a block from the stack + Label l = stack; + stack = stack.next; + // computes the true (non relative) max stack size of this block + int start = l.inputStackTop; + int blockMax = start + l.outputStackMax; + // updates the global max stack size + if (blockMax > max) { + max = blockMax; + } + // analyzes the successors of the block + Edge b = l.successors; + if ((l.status & Label.JSR) != 0) { + // ignores the first edge of JSR blocks (virtual successor) + b = b.next; + } + while (b != null) { + l = b.successor; + // if this successor has not already been pushed... + if ((l.status & Label.PUSHED) == 0) { + // computes its true beginning stack size... + l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start + + b.info; + // ...and pushes it onto the stack + l.status |= Label.PUSHED; + l.next = stack; + stack = l; + } + b = b.next; + } + } + this.maxStack = Math.max(maxStack, max); + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods: control flow analysis algorithm + // ------------------------------------------------------------------------ + + /** + * Adds a successor to the {@link #currentBlock currentBlock} block. + * + * @param info + * information about the control flow edge to be added. + * @param successor + * the successor block to be added to the current block. + */ + private void addSuccessor(final int info, final Label successor) { + // creates and initializes an Edge object... + Edge b = new Edge(); + b.info = info; + b.successor = successor; + // ...and adds it to the successor list of the currentBlock block + b.next = currentBlock.successors; + currentBlock.successors = b; + } + + /** + * Ends the current basic block. This method must be used in the case where + * the current basic block does not have any successor. + */ + private void noSuccessor() { + if (compute == FRAMES) { + Label l = new Label(); + l.frame = new Frame(); + l.frame.owner = l; + l.resolve(this, code.length, code.data); + previousBlock.successor = l; + previousBlock = l; + } else { + currentBlock.outputStackMax = maxStackSize; + } + currentBlock = null; + } + + // ------------------------------------------------------------------------ + // Utility methods: stack map frames + // ------------------------------------------------------------------------ + + /** + * Visits a frame that has been computed from scratch. + * + * @param f + * the frame that must be visited. + */ + private void visitFrame(final Frame f) { + int i, t; + int nTop = 0; + int nLocal = 0; + int nStack = 0; + int[] locals = f.inputLocals; + int[] stacks = f.inputStack; + // computes the number of locals (ignores TOP types that are just after + // a LONG or a DOUBLE, and all trailing TOP types) + for (i = 0; i < locals.length; ++i) { + t = locals[i]; + if (t == Frame.TOP) { + ++nTop; + } else { + nLocal += nTop + 1; + nTop = 0; + } + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // computes the stack size (ignores TOP types that are just after + // a LONG or a DOUBLE) + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + ++nStack; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // visits the frame and its content + int frameIndex = startFrame(f.owner.position, nLocal, nStack); + for (i = 0; nLocal > 0; ++i, --nLocal) { + t = locals[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + endFrame(); + } + + /** + * Visit the implicit first frame of this method. + */ + private void visitImplicitFirstFrame() { + // There can be at most descriptor.length() + 1 locals + int frameIndex = startFrame(0, descriptor.length() + 1, 0); + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & ACC_CONSTRUCTOR) == 0) { + frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName); + } else { + frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS; + } + } + int i = 1; + loop: while (true) { + int j = i; + switch (descriptor.charAt(i++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + frame[frameIndex++] = 1; // Opcodes.INTEGER; + break; + case 'F': + frame[frameIndex++] = 2; // Opcodes.FLOAT; + break; + case 'J': + frame[frameIndex++] = 4; // Opcodes.LONG; + break; + case 'D': + frame[frameIndex++] = 3; // Opcodes.DOUBLE; + break; + case '[': + while (descriptor.charAt(i) == '[') { + ++i; + } + if (descriptor.charAt(i) == 'L') { + ++i; + while (descriptor.charAt(i) != ';') { + ++i; + } + } + frame[frameIndex++] = Frame.OBJECT + | cw.addType(descriptor.substring(j, ++i)); + break; + case 'L': + while (descriptor.charAt(i) != ';') { + ++i; + } + frame[frameIndex++] = Frame.OBJECT + | cw.addType(descriptor.substring(j + 1, i++)); + break; + default: + break loop; + } + } + frame[1] = frameIndex - 3; + endFrame(); + } + + /** + * Starts the visit of a stack map frame. + * + * @param offset + * the offset of the instruction to which the frame corresponds. + * @param nLocal + * the number of local variables in the frame. + * @param nStack + * the number of stack elements in the frame. + * @return the index of the next element to be written in this frame. + */ + private int startFrame(final int offset, final int nLocal, final int nStack) { + int n = 3 + nLocal + nStack; + if (frame == null || frame.length < n) { + frame = new int[n]; + } + frame[0] = offset; + frame[1] = nLocal; + frame[2] = nStack; + return 3; + } + + /** + * Checks if the visit of the current frame {@link #frame} is finished, and + * if yes, write it in the StackMapTable attribute. + */ + private void endFrame() { + if (previousFrame != null) { // do not write the first frame + if (stackMap == null) { + stackMap = new ByteVector(); + } + writeFrame(); + ++frameCount; + } + previousFrame = frame; + frame = null; + } + + /** + * Compress and writes the current frame {@link #frame} in the StackMapTable + * attribute. + */ + private void writeFrame() { + int clocalsSize = frame[1]; + int cstackSize = frame[2]; + if ((cw.version & 0xFFFF) < Opcodes.V1_6) { + stackMap.putShort(frame[0]).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + return; + } + int localsSize = previousFrame[1]; + int type = FULL_FRAME; + int k = 0; + int delta; + if (frameCount == 0) { + delta = frame[0]; + } else { + delta = frame[0] - previousFrame[0] - 1; + } + if (cstackSize == 0) { + k = clocalsSize - localsSize; + switch (k) { + case -3: + case -2: + case -1: + type = CHOP_FRAME; + localsSize = clocalsSize; + break; + case 0: + type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = APPEND_FRAME; + break; + } + } else if (clocalsSize == localsSize && cstackSize == 1) { + type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME + : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != FULL_FRAME) { + // verify if locals are the same + int l = 3; + for (int j = 0; j < localsSize; j++) { + if (frame[l] != previousFrame[l]) { + type = FULL_FRAME; + break; + } + l++; + } + } + switch (type) { + case SAME_FRAME: + stackMap.putByte(delta); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort( + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_FRAME_EXTENDED: + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + break; + case CHOP_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + break; + case APPEND_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + writeFrameTypes(3 + localsSize, 3 + clocalsSize); + break; + // case FULL_FRAME: + default: + stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + } + } + + /** + * Writes some types of the current frame {@link #frame} into the + * StackMapTableAttribute. This method converts types from the format used + * in {@link Label} to the format used in StackMapTable attributes. In + * particular, it converts type table indexes to constant pool indexes. + * + * @param start + * index of the first type in {@link #frame} to write. + * @param end + * index of last type in {@link #frame} to write (exclusive). + */ + private void writeFrameTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + int t = frame[i]; + int d = t & Frame.DIM; + if (d == 0) { + int v = t & Frame.BASE_VALUE; + switch (t & Frame.BASE_KIND) { + case Frame.OBJECT: + stackMap.putByte(7).putShort( + cw.newClass(cw.typeTable[v].strVal1)); + break; + case Frame.UNINITIALIZED: + stackMap.putByte(8).putShort(cw.typeTable[v].intVal); + break; + default: + stackMap.putByte(v); + } + } else { + StringBuilder sb = new StringBuilder(); + d >>= 28; + while (d-- > 0) { + sb.append('['); + } + if ((t & Frame.BASE_KIND) == Frame.OBJECT) { + sb.append('L'); + sb.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + sb.append(';'); + } else { + switch (t & 0xF) { + case 1: + sb.append('I'); + break; + case 2: + sb.append('F'); + break; + case 3: + sb.append('D'); + break; + case 9: + sb.append('Z'); + break; + case 10: + sb.append('B'); + break; + case 11: + sb.append('C'); + break; + case 12: + sb.append('S'); + break; + default: + sb.append('J'); + } + } + stackMap.putByte(7).putShort(cw.newClass(sb.toString())); + } + } + } + + private void writeFrameType(final Object type) { + if (type instanceof String) { + stackMap.putByte(7).putShort(cw.newClass((String) type)); + } else if (type instanceof Integer) { + stackMap.putByte(((Integer) type).intValue()); + } else { + stackMap.putByte(8).putShort(((Label) type).position); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: dump bytecode array + // ------------------------------------------------------------------------ + + /** + * Returns the size of the bytecode of this method. + * + * @return the size of the bytecode of this method. + */ + final int getSize() { + if (classReaderOffset != 0) { + return 6 + classReaderLength; + } + int size = 8; + if (code.length > 0) { + if (code.length > 65536) { + throw new RuntimeException("Method code too large!"); + } + cw.newUTF8("Code"); + size += 18 + code.length + 8 * handlerCount; + if (localVar != null) { + cw.newUTF8("LocalVariableTable"); + size += 8 + localVar.length; + } + if (localVarType != null) { + cw.newUTF8("LocalVariableTypeTable"); + size += 8 + localVarType.length; + } + if (lineNumber != null) { + cw.newUTF8("LineNumberTable"); + size += 8 + lineNumber.length; + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + cw.newUTF8(zip ? "StackMapTable" : "StackMap"); + size += 8 + stackMap.length; + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + ctanns.getSize(); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + ictanns.getSize(); + } + if (cattrs != null) { + size += cattrs.getSize(cw, code.data, code.length, maxStack, + maxLocals); + } + } + if (exceptionCount > 0) { + cw.newUTF8("Exceptions"); + size += 8 + 2 * exceptionCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + cw.newUTF8("Synthetic"); + size += 6; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != null) { + cw.newUTF8("Signature"); + cw.newUTF8(signature); + size += 8; + } + if (methodParameters != null) { + cw.newUTF8("MethodParameters"); + size += 7 + methodParameters.length; + } + if (ClassReader.ANNOTATIONS && annd != null) { + cw.newUTF8("AnnotationDefault"); + size += 6 + annd.length; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + tanns.getSize(); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + itanns.getSize(); + } + if (ClassReader.ANNOTATIONS && panns != null) { + cw.newUTF8("RuntimeVisibleParameterAnnotations"); + size += 7 + 2 * (panns.length - synthetics); + for (int i = panns.length - 1; i >= synthetics; --i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + cw.newUTF8("RuntimeInvisibleParameterAnnotations"); + size += 7 + 2 * (ipanns.length - synthetics); + for (int i = ipanns.length - 1; i >= synthetics; --i) { + size += ipanns[i] == null ? 0 : ipanns[i].getSize(); + } + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the bytecode of this method in the given byte vector. + * + * @param out + * the byte vector into which the bytecode of this method must be + * copied. + */ + final void put(final ByteVector out) { + final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; + int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); + out.putShort(access & ~mask).putShort(name).putShort(desc); + if (classReaderOffset != 0) { + out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength); + return; + } + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (exceptionCount > 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != null) { + ++attributeCount; + } + if (methodParameters != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && annd != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && panns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (code.length > 0) { + int size = 12 + code.length + 8 * handlerCount; + if (localVar != null) { + size += 8 + localVar.length; + } + if (localVarType != null) { + size += 8 + localVarType.length; + } + if (lineNumber != null) { + size += 8 + lineNumber.length; + } + if (stackMap != null) { + size += 8 + stackMap.length; + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + size += 8 + ctanns.getSize(); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + size += 8 + ictanns.getSize(); + } + if (cattrs != null) { + size += cattrs.getSize(cw, code.data, code.length, maxStack, + maxLocals); + } + out.putShort(cw.newUTF8("Code")).putInt(size); + out.putShort(maxStack).putShort(maxLocals); + out.putInt(code.length).putByteArray(code.data, 0, code.length); + out.putShort(handlerCount); + if (handlerCount > 0) { + Handler h = firstHandler; + while (h != null) { + out.putShort(h.start.position).putShort(h.end.position) + .putShort(h.handler.position).putShort(h.type); + h = h.next; + } + } + attributeCount = 0; + if (localVar != null) { + ++attributeCount; + } + if (localVarType != null) { + ++attributeCount; + } + if (lineNumber != null) { + ++attributeCount; + } + if (stackMap != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + ++attributeCount; + } + if (cattrs != null) { + attributeCount += cattrs.getCount(); + } + out.putShort(attributeCount); + if (localVar != null) { + out.putShort(cw.newUTF8("LocalVariableTable")); + out.putInt(localVar.length + 2).putShort(localVarCount); + out.putByteArray(localVar.data, 0, localVar.length); + } + if (localVarType != null) { + out.putShort(cw.newUTF8("LocalVariableTypeTable")); + out.putInt(localVarType.length + 2).putShort(localVarTypeCount); + out.putByteArray(localVarType.data, 0, localVarType.length); + } + if (lineNumber != null) { + out.putShort(cw.newUTF8("LineNumberTable")); + out.putInt(lineNumber.length + 2).putShort(lineNumberCount); + out.putByteArray(lineNumber.data, 0, lineNumber.length); + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap")); + out.putInt(stackMap.length + 2).putShort(frameCount); + out.putByteArray(stackMap.data, 0, stackMap.length); + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + ctanns.put(out); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + ictanns.put(out); + } + if (cattrs != null) { + cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); + } + } + if (exceptionCount > 0) { + out.putShort(cw.newUTF8("Exceptions")).putInt( + 2 * exceptionCount + 2); + out.putShort(exceptionCount); + for (int i = 0; i < exceptionCount; ++i) { + out.putShort(exceptions[i]); + } + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != null) { + out.putShort(cw.newUTF8("Signature")).putInt(2) + .putShort(cw.newUTF8(signature)); + } + if (methodParameters != null) { + out.putShort(cw.newUTF8("MethodParameters")); + out.putInt(methodParameters.length + 1).putByte( + methodParametersCount); + out.putByteArray(methodParameters.data, 0, methodParameters.length); + } + if (ClassReader.ANNOTATIONS && annd != null) { + out.putShort(cw.newUTF8("AnnotationDefault")); + out.putInt(annd.length); + out.putByteArray(annd.data, 0, annd.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } + if (ClassReader.ANNOTATIONS && panns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); + AnnotationWriter.put(panns, synthetics, out); + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations")); + AnnotationWriter.put(ipanns, synthetics, out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) + // ------------------------------------------------------------------------ + + /** + * Resizes and replaces the temporary instructions inserted by + * {@link Label#resolve} for wide forward jumps, while keeping jump offsets + * and instruction addresses consistent. This may require to resize other + * existing instructions, or even to introduce new instructions: for + * example, increasing the size of an instruction by 2 at the middle of a + * method can increases the offset of an IFEQ instruction from 32766 to + * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W + * 32765. This, in turn, may require to increase the size of another jump + * instruction, and so on... All these operations are handled automatically + * by this method. + * <p> + * <i>This method must be called after all the method that is being built + * has been visited</i>. In particular, the {@link Label Label} objects used + * to construct the method are no longer valid after this method has been + * called. + */ + private void resizeInstructions() { + byte[] b = code.data; // bytecode of the method + int u, v, label; // indexes in b + int i, j; // loop indexes + /* + * 1st step: As explained above, resizing an instruction may require to + * resize another one, which may require to resize yet another one, and + * so on. The first step of the algorithm consists in finding all the + * instructions that need to be resized, without modifying the code. + * This is done by the following "fix point" algorithm: + * + * Parse the code to find the jump instructions whose offset will need + * more than 2 bytes to be stored (the future offset is computed from + * the current offset and from the number of bytes that will be inserted + * or removed between the source and target instructions). For each such + * instruction, adds an entry in (a copy of) the indexes and sizes + * arrays (if this has not already been done in a previous iteration!). + * + * If at least one entry has been added during the previous step, go + * back to the beginning, otherwise stop. + * + * In fact the real algorithm is complicated by the fact that the size + * of TABLESWITCH and LOOKUPSWITCH instructions depends on their + * position in the bytecode (because of padding). In order to ensure the + * convergence of the algorithm, the number of bytes to be added or + * removed from these instructions is over estimated during the previous + * loop, and computed exactly only after the loop is finished (this + * requires another pass to parse the bytecode of the method). + */ + int[] allIndexes = new int[0]; // copy of indexes + int[] allSizes = new int[0]; // copy of sizes + boolean[] resize; // instructions to be resized + int newOffset; // future offset of a jump instruction + + resize = new boolean[code.length]; + + // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done + int state = 3; + do { + if (state == 3) { + state = 2; + } + u = 0; + while (u < b.length) { + int opcode = b[u] & 0xFF; // opcode of current instruction + int insert = 0; // bytes to be added after this instruction + + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // converts temporary opcodes 202 to 217, 218 and + // 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (newOffset < Short.MIN_VALUE + || newOffset > Short.MAX_VALUE) { + if (!resize[u]) { + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // two additional bytes will be required to + // replace this GOTO or JSR instruction with + // a GOTO_W or a JSR_W + insert = 2; + } else { + // five additional bytes will be required to + // replace this IFxxx <l> instruction with + // IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx + // is the "opposite" opcode of IFxxx (i.e., + // IFNE for IFEQ) and where <l'> designates + // the instruction just after the GOTO_W. + insert = 5; + } + resize[u] = true; + } + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + u += 5; + break; + case ClassWriter.TABL_INSN: + if (state == 1) { + // true number of bytes to be added (or removed) + // from this instruction = (future number of padding + // bytes - current number of padding byte) - + // previously over estimated variation = + // = ((3 - newOffset%4) - (3 - u%4)) - u%4 + // = (-newOffset%4 + u%4) - u%4 + // = -(newOffset & 3) + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // over estimation of the number of bytes to be + // added to this instruction = 3 - current number + // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; + break; + case ClassWriter.LOOK_INSN: + if (state == 1) { + // like TABL_INSN + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // like TABL_INSN + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 8 * readInt(b, u + 4) + 8; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case ClassWriter.MANA_INSN: + default: + u += 4; + break; + } + if (insert != 0) { + // adds a new (u, insert) entry in the allIndexes and + // allSizes arrays + int[] newIndexes = new int[allIndexes.length + 1]; + int[] newSizes = new int[allSizes.length + 1]; + System.arraycopy(allIndexes, 0, newIndexes, 0, + allIndexes.length); + System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); + newIndexes[allIndexes.length] = u; + newSizes[allSizes.length] = insert; + allIndexes = newIndexes; + allSizes = newSizes; + if (insert > 0) { + state = 3; + } + } + } + if (state < 3) { + --state; + } + } while (state != 0); + + // 2nd step: + // copies the bytecode of the method into a new bytevector, updates the + // offsets, and inserts (or removes) bytes as requested. + + ByteVector newCode = new ByteVector(code.length); + + u = 0; + while (u < code.length) { + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + newCode.putByte(opcode); + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (resize[u]) { + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where <l'> designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO) { + newCode.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + newCode.putByte(201); // JSR_W + } else { + newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + newCode.putShort(8); // jump offset + newCode.putByte(200); // GOTO_W + // newOffset now computed from start of GOTO_W + newOffset -= 3; + } + newCode.putInt(newOffset); + } else { + newCode.putByte(opcode); + newCode.putShort(newOffset); + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + label = u + readInt(b, u + 1); + newOffset = getNewOffset(allIndexes, allSizes, u, label); + newCode.putByte(opcode); + newCode.putInt(newOffset); + u += 5; + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.TABLESWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + j = readInt(b, u) - j + 1; + u += 4; + newCode.putInt(readInt(b, u - 4)); + for (; j > 0; --j) { + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.LOOKUPSWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + for (; j > 0; --j) { + newCode.putInt(readInt(b, u)); + u += 4; + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + newCode.putByteArray(b, u, 6); + u += 6; + } else { + newCode.putByteArray(b, u, 4); + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + newCode.putByteArray(b, u, 2); + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + newCode.putByteArray(b, u, 3); + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + newCode.putByteArray(b, u, 5); + u += 5; + break; + // case MANA_INSN: + default: + newCode.putByteArray(b, u, 4); + u += 4; + break; + } + } + + // updates the stack map frame labels + if (compute == FRAMES) { + Label l = labels; + while (l != null) { + /* + * Detects the labels that are just after an IF instruction that + * has been resized with the IFNOT GOTO_W pattern. These labels + * are now the target of a jump instruction (the IFNOT + * instruction). Note that we need the original label position + * here. getNewOffset must therefore never have been called for + * this label. + */ + u = l.position - 3; + if (u >= 0 && resize[u]) { + l.status |= Label.TARGET; + } + getNewOffset(allIndexes, allSizes, l); + l = l.successor; + } + // Update the offsets in the uninitialized types + for (i = 0; i < cw.typeTable.length; ++i) { + Item item = cw.typeTable[i]; + if (item != null && item.type == ClassWriter.TYPE_UNINIT) { + item.intVal = getNewOffset(allIndexes, allSizes, 0, + item.intVal); + } + } + // The stack map frames are not serialized yet, so we don't need + // to update them. They will be serialized in visitMaxs. + } else if (frameCount > 0) { + /* + * Resizing an existing stack map frame table is really hard. Not + * only the table must be parsed to update the offets, but new + * frames may be needed for jump instructions that were inserted by + * this method. And updating the offsets or inserting frames can + * change the format of the following frames, in case of packed + * frames. In practice the whole table must be recomputed. For this + * the frames are marked as potentially invalid. This will cause the + * whole class to be reread and rewritten with the COMPUTE_FRAMES + * option (see the ClassWriter.toByteArray method). This is not very + * efficient but is much easier and requires much less code than any + * other method I can think of. + */ + cw.invalidFrames = true; + } + // updates the exception handler block labels + Handler h = firstHandler; + while (h != null) { + getNewOffset(allIndexes, allSizes, h.start); + getNewOffset(allIndexes, allSizes, h.end); + getNewOffset(allIndexes, allSizes, h.handler); + h = h.next; + } + // updates the instructions addresses in the + // local var and line number tables + for (i = 0; i < 2; ++i) { + ByteVector bv = i == 0 ? localVar : localVarType; + if (bv != null) { + b = bv.data; + u = 0; + while (u < bv.length) { + label = readUnsignedShort(b, u); + newOffset = getNewOffset(allIndexes, allSizes, 0, label); + writeShort(b, u, newOffset); + label += readUnsignedShort(b, u + 2); + newOffset = getNewOffset(allIndexes, allSizes, 0, label) + - newOffset; + writeShort(b, u + 2, newOffset); + u += 10; + } + } + } + if (lineNumber != null) { + b = lineNumber.data; + u = 0; + while (u < lineNumber.length) { + writeShort( + b, + u, + getNewOffset(allIndexes, allSizes, 0, + readUnsignedShort(b, u))); + u += 4; + } + } + // updates the labels of the other attributes + Attribute attr = cattrs; + while (attr != null) { + Label[] labels = attr.getLabels(); + if (labels != null) { + for (i = labels.length - 1; i >= 0; --i) { + getNewOffset(allIndexes, allSizes, labels[i]); + } + } + attr = attr.next; + } + + // replaces old bytecodes with new ones + code = newCode; + } + + /** + * Reads an unsigned short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static int readUnsignedShort(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static short readShort(final byte[] b, final int index) { + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static int readInt(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Writes a short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * where the first byte of the short value must be written. + * @param s + * the value to be written in the given byte array. + */ + static void writeShort(final byte[] b, final int index, final int s) { + b[index] = (byte) (s >>> 8); + b[index + 1] = (byte) s; + } + + /** + * Computes the future value of a bytecode offset. + * <p> + * Note: it is possible to have several entries for the same instruction in + * the <tt>indexes</tt> and <tt>sizes</tt>: two entries (index=a,size=b) and + * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b'). + * + * @param indexes + * current positions of the instructions to be resized. Each + * instruction must be designated by the index of its <i>last</i> + * byte, plus one (or, in other words, by the index of the + * <i>first</i> byte of the <i>next</i> instruction). + * @param sizes + * the number of bytes to be <i>added</i> to the above + * instructions. More precisely, for each i < <tt>len</tt>, + * <tt>sizes</tt>[i] bytes will be added at the end of the + * instruction designated by <tt>indexes</tt>[i] or, if + * <tt>sizes</tt>[i] is negative, the <i>last</i> | + * <tt>sizes[i]</tt>| bytes of the instruction will be removed + * (the instruction size <i>must not</i> become negative or + * null). + * @param begin + * index of the first byte of the source instruction. + * @param end + * index of the first byte of the target instruction. + * @return the future value of the given bytecode offset. + */ + static int getNewOffset(final int[] indexes, final int[] sizes, + final int begin, final int end) { + int offset = end - begin; + for (int i = 0; i < indexes.length; ++i) { + if (begin < indexes[i] && indexes[i] <= end) { + // forward jump + offset += sizes[i]; + } else if (end < indexes[i] && indexes[i] <= begin) { + // backward jump + offset -= sizes[i]; + } + } + return offset; + } + + /** + * Updates the offset of the given label. + * + * @param indexes + * current positions of the instructions to be resized. Each + * instruction must be designated by the index of its <i>last</i> + * byte, plus one (or, in other words, by the index of the + * <i>first</i> byte of the <i>next</i> instruction). + * @param sizes + * the number of bytes to be <i>added</i> to the above + * instructions. More precisely, for each i < <tt>len</tt>, + * <tt>sizes</tt>[i] bytes will be added at the end of the + * instruction designated by <tt>indexes</tt>[i] or, if + * <tt>sizes</tt>[i] is negative, the <i>last</i> | + * <tt>sizes[i]</tt>| bytes of the instruction will be removed + * (the instruction size <i>must not</i> become negative or + * null). + * @param label + * the label whose offset must be updated. + */ + static void getNewOffset(final int[] indexes, final int[] sizes, + final Label label) { + if ((label.status & Label.RESIZED) == 0) { + label.position = getNewOffset(indexes, sizes, 0, label.position); + label.status |= Label.RESIZED; + } + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/Opcodes.java b/spring-core/src/main/java/org/springframework/asm/Opcodes.java new file mode 100644 index 00000000..1f4f4825 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java @@ -0,0 +1,361 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +/** + * Defines the JVM opcodes, access flags and array type codes. This interface + * does not define all the JVM opcodes because some opcodes are automatically + * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced + * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n + * opcodes are therefore not defined in this interface. Likewise for LDC, + * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and + * JSR_W. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public interface Opcodes { + + // ASM API versions + + int ASM4 = 4 << 16 | 0 << 8 | 0; + int ASM5 = 5 << 16 | 0 << 8 | 0; + + // versions + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + int V1_8 = 0 << 16 | 52; + + // access flags + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method, parameter + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + int ACC_MANDATED = 0x8000; // parameter + + // ASM specific pseudo access flags + + int ACC_DEPRECATED = 0x20000; // class, field, method + + // types for NEWARRAY + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // tags for Handle + + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; + + // stack map frame types + + /** + * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}. + */ + int F_NEW = -1; + + /** + * Represents a compressed frame with complete frame data. + */ + int F_FULL = 0; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that additional 1-3 locals are defined, and + * with an empty stack. + */ + int F_APPEND = 1; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that the last 1-3 locals are absent and with + * an empty stack. + */ + int F_CHOP = 2; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with a single value on the stack. + */ + int F_SAME1 = 4; + + Integer TOP = new Integer(0); + Integer INTEGER = new Integer(1); + Integer FLOAT = new Integer(2); + Integer DOUBLE = new Integer(3); + Integer LONG = new Integer(4); + Integer NULL = new Integer(5); + Integer UNINITIALIZED_THIS = new Integer(6); + + // opcodes // visit method (- = idem) + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + // int LDC_W = 19; // - + // int LDC2_W = 20; // - + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + // int ILOAD_0 = 26; // - + // int ILOAD_1 = 27; // - + // int ILOAD_2 = 28; // - + // int ILOAD_3 = 29; // - + // int LLOAD_0 = 30; // - + // int LLOAD_1 = 31; // - + // int LLOAD_2 = 32; // - + // int LLOAD_3 = 33; // - + // int FLOAD_0 = 34; // - + // int FLOAD_1 = 35; // - + // int FLOAD_2 = 36; // - + // int FLOAD_3 = 37; // - + // int DLOAD_0 = 38; // - + // int DLOAD_1 = 39; // - + // int DLOAD_2 = 40; // - + // int DLOAD_3 = 41; // - + // int ALOAD_0 = 42; // - + // int ALOAD_1 = 43; // - + // int ALOAD_2 = 44; // - + // int ALOAD_3 = 45; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + // int ISTORE_0 = 59; // - + // int ISTORE_1 = 60; // - + // int ISTORE_2 = 61; // - + // int ISTORE_3 = 62; // - + // int LSTORE_0 = 63; // - + // int LSTORE_1 = 64; // - + // int LSTORE_2 = 65; // - + // int LSTORE_3 = 66; // - + // int FSTORE_0 = 67; // - + // int FSTORE_1 = 68; // - + // int FSTORE_2 = 69; // - + // int FSTORE_3 = 70; // - + // int DSTORE_0 = 71; // - + // int DSTORE_1 = 72; // - + // int DSTORE_2 = 73; // - + // int DSTORE_3 = 74; // - + // int ASTORE_0 = 75; // - + // int ASTORE_1 = 76; // - + // int ASTORE_2 = 77; // - + // int ASTORE_3 = 78; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + // int WIDE = 196; // NOT VISITED + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - + // int GOTO_W = 200; // - + // int JSR_W = 201; // - +} diff --git a/spring-core/src/main/java/org/springframework/asm/Type.java b/spring-core/src/main/java/org/springframework/asm/Type.java new file mode 100644 index 00000000..6a835c25 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/Type.java @@ -0,0 +1,896 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java field or method type. This class can be used to make it easier to + * manipulate type and method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +public class Type { + + /** + * The sort of the <tt>void</tt> type. See {@link #getSort getSort}. + */ + public static final int VOID = 0; + + /** + * The sort of the <tt>boolean</tt> type. See {@link #getSort getSort}. + */ + public static final int BOOLEAN = 1; + + /** + * The sort of the <tt>char</tt> type. See {@link #getSort getSort}. + */ + public static final int CHAR = 2; + + /** + * The sort of the <tt>byte</tt> type. See {@link #getSort getSort}. + */ + public static final int BYTE = 3; + + /** + * The sort of the <tt>short</tt> type. See {@link #getSort getSort}. + */ + public static final int SHORT = 4; + + /** + * The sort of the <tt>int</tt> type. See {@link #getSort getSort}. + */ + public static final int INT = 5; + + /** + * The sort of the <tt>float</tt> type. See {@link #getSort getSort}. + */ + public static final int FLOAT = 6; + + /** + * The sort of the <tt>long</tt> type. See {@link #getSort getSort}. + */ + public static final int LONG = 7; + + /** + * The sort of the <tt>double</tt> type. See {@link #getSort getSort}. + */ + public static final int DOUBLE = 8; + + /** + * The sort of array reference types. See {@link #getSort getSort}. + */ + public static final int ARRAY = 9; + + /** + * The sort of object reference types. See {@link #getSort getSort}. + */ + public static final int OBJECT = 10; + + /** + * The sort of method types. See {@link #getSort getSort}. + */ + public static final int METHOD = 11; + + /** + * The <tt>void</tt> type. + */ + public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24) + | (5 << 16) | (0 << 8) | 0, 1); + + /** + * The <tt>boolean</tt> type. + */ + public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The <tt>char</tt> type. + */ + public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) + | (0 << 16) | (6 << 8) | 1, 1); + + /** + * The <tt>byte</tt> type. + */ + public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The <tt>short</tt> type. + */ + public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) + | (0 << 16) | (7 << 8) | 1, 1); + + /** + * The <tt>int</tt> type. + */ + public static final Type INT_TYPE = new Type(INT, null, ('I' << 24) + | (0 << 16) | (0 << 8) | 1, 1); + + /** + * The <tt>float</tt> type. + */ + public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) + | (2 << 16) | (2 << 8) | 1, 1); + + /** + * The <tt>long</tt> type. + */ + public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24) + | (1 << 16) | (1 << 8) | 2, 1); + + /** + * The <tt>double</tt> type. + */ + public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) + | (3 << 16) | (3 << 8) | 2, 1); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * The sort of this Java type. + */ + private final int sort; + + /** + * A buffer containing the internal name of this Java type. This field is + * only used for reference types. + */ + private final char[] buf; + + /** + * The offset of the internal name of this Java type in {@link #buf buf} or, + * for primitive types, the size, descriptor and getOpcode offsets for this + * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset + * for IALOAD or IASTORE, byte 3 the offset for all other instructions). + */ + private final int off; + + /** + * The length of the internal name of this Java type. + */ + private final int len; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a reference type. + * + * @param sort + * the sort of the reference type to be constructed. + * @param buf + * a buffer containing the descriptor of the previous type. + * @param off + * the offset of this descriptor in the previous buffer. + * @param len + * the length of this descriptor. + */ + private Type(final int sort, final char[] buf, final int off, final int len) { + this.sort = sort; + this.buf = buf; + this.off = off; + this.len = len; + } + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param typeDescriptor + * a field or method type descriptor. + * @return the Java type corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getType(typeDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java type corresponding to the given internal name. + * + * @param internalName + * an internal name. + * @return the Java type corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + char[] buf = internalName.toCharArray(); + return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length); + } + + /** + * Returns the Java type corresponding to the given method descriptor. + * Equivalent to <code>Type.getType(methodDescriptor)</code>. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java type corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return getType(methodDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java method type corresponding to the given argument and + * return types. + * + * @param returnType + * the return type of the method. + * @param argumentTypes + * the argument types of the method. + * @return the Java type corresponding to the given argument and return + * types. + */ + public static Type getMethodType(final Type returnType, + final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the Java type corresponding to the given class. + * + * @param c + * a class. + * @return the Java type corresponding to the given class. + */ + public static Type getType(final Class<?> c) { + if (c.isPrimitive()) { + if (c == Integer.TYPE) { + return INT_TYPE; + } else if (c == Void.TYPE) { + return VOID_TYPE; + } else if (c == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (c == Byte.TYPE) { + return BYTE_TYPE; + } else if (c == Character.TYPE) { + return CHAR_TYPE; + } else if (c == Short.TYPE) { + return SHORT_TYPE; + } else if (c == Double.TYPE) { + return DOUBLE_TYPE; + } else if (c == Float.TYPE) { + return FLOAT_TYPE; + } else /* if (c == Long.TYPE) */{ + return LONG_TYPE; + } + } else { + return getType(getDescriptor(c)); + } + } + + /** + * Returns the Java method type corresponding to the given constructor. + * + * @param c + * a {@link Constructor Constructor} object. + * @return the Java method type corresponding to the given constructor. + */ + public static Type getType(final Constructor<?> c) { + return getType(getConstructorDescriptor(c)); + } + + /** + * Returns the Java method type corresponding to the given method. + * + * @param m + * a {@link Method Method} object. + * @return the Java method type corresponding to the given method. + */ + public static Type getType(final Method m) { + return getType(getMethodDescriptor(m)); + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java types corresponding to the argument types of the given + * method descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + int off = 1; + int size = 0; + while (true) { + char car = buf[off++]; + if (car == ')') { + break; + } else if (car == 'L') { + while (buf[off++] != ';') { + } + ++size; + } else if (car != '[') { + ++size; + } + } + Type[] args = new Type[size]; + off = 1; + size = 0; + while (buf[off] != ')') { + args[size] = getType(buf, off); + off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); + size += 1; + } + return args; + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method. + * + * @param method + * a method. + * @return the Java types corresponding to the argument types of the given + * method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class<?>[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the Java type corresponding to the return type of the given + * method descriptor. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java type corresponding to the return type of the given + * method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + return getType(buf, methodDescriptor.indexOf(')') + 1); + } + + /** + * Returns the Java type corresponding to the return type of the given + * method. + * + * @param method + * a method. + * @return the Java type corresponding to the return type of the given + * method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param desc + * the descriptor of a method. + * @return the size of the arguments of the method (plus one for the + * implicit this argument), argSize, and the size of its return + * value, retSize, packed into a single int i = + * <tt>(argSize << 2) | retSize</tt> (argSize is therefore equal to + * <tt>i >> 2</tt>, and retSize to <tt>i & 0x03</tt>). + */ + public static int getArgumentsAndReturnSizes(final String desc) { + int n = 1; + int c = 1; + while (true) { + char car = desc.charAt(c++); + if (car == ')') { + car = desc.charAt(c); + return n << 2 + | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1)); + } else if (car == 'L') { + while (desc.charAt(c++) != ';') { + } + n += 1; + } else if (car == '[') { + while ((car = desc.charAt(c)) == '[') { + ++c; + } + if (car == 'D' || car == 'J') { + n -= 1; + } + } else if (car == 'D' || car == 'J') { + n += 2; + } else { + n += 1; + } + } + } + + /** + * Returns the Java type corresponding to the given type descriptor. For + * method descriptors, buf is supposed to contain nothing more than the + * descriptor itself. + * + * @param buf + * a buffer containing a type descriptor. + * @param off + * the offset of this descriptor in the previous buffer. + * @return the Java type corresponding to the given type descriptor. + */ + private static Type getType(final char[] buf, final int off) { + int len; + switch (buf[off]) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + len = 1; + while (buf[off + len] == '[') { + ++len; + } + if (buf[off + len] == 'L') { + ++len; + while (buf[off + len] != ';') { + ++len; + } + } + return new Type(ARRAY, buf, off, len + 1); + case 'L': + len = 1; + while (buf[off + len] != ';') { + ++len; + } + return new Type(OBJECT, buf, off + 1, len - 1); + // case '(': + default: + return new Type(METHOD, buf, off, buf.length - off); + } + } + + // ------------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------------ + + /** + * Returns the sort of this Java type. + * + * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR}, + * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT}, + * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE}, + * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD + * METHOD}. + */ + public int getSort() { + return sort; + } + + /** + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int i = 1; + while (buf[off + i] == '[') { + ++i; + } + return i; + } + + /** + * Returns the type of the elements of this array type. This method should + * only be used for an array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + return getType(buf, off + getDimensions()); + } + + /** + * Returns the binary name of the class corresponding to this type. This + * method must not be used on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuilder sb = new StringBuilder(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + sb.append("[]"); + } + return sb.toString(); + case OBJECT: + return new String(buf, off, len).replace('/', '.'); + default: + return null; + } + } + + /** + * Returns the internal name of the class corresponding to this object or + * array type. The internal name of a class is its fully qualified name (as + * returned by Class.getName(), where '.' are replaced by '/'. This method + * should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return new String(buf, off, len); + } + + /** + * Returns the argument types of methods of this type. This method should + * only be used for method types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * Returns the return type of methods of this type. This method should only + * be used for method types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the size of the arguments and of the return value of methods of + * this type. This method should only be used for method types. + * + * @return the size of the arguments (plus one for the implicit this + * argument), argSize, and the size of the return value, retSize, + * packed into a single + * int i = <tt>(argSize << 2) | retSize</tt> + * (argSize is therefore equal to <tt>i >> 2</tt>, + * and retSize to <tt>i & 0x03</tt>). + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /** + * Returns the descriptor corresponding to this Java type. + * + * @return the descriptor corresponding to this Java type. + */ + public String getDescriptor() { + StringBuilder sb = new StringBuilder(); + getDescriptor(sb); + return sb.toString(); + } + + /** + * Returns the descriptor corresponding to the given argument and return + * types. + * + * @param returnType + * the return type of the method. + * @param argumentTypes + * the argument types of the method. + * @return the descriptor corresponding to the given argument and return + * types. + */ + public static String getMethodDescriptor(final Type returnType, + final Type... argumentTypes) { + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (int i = 0; i < argumentTypes.length; ++i) { + argumentTypes[i].getDescriptor(sb); + } + sb.append(')'); + returnType.getDescriptor(sb); + return sb.toString(); + } + + /** + * Appends the descriptor corresponding to this Java type to the given + * string builder. + * + * @param sb + * the string builder to which the descriptor must be appended. + */ + private void getDescriptor(final StringBuilder sb) { + if (this.buf == null) { + // descriptor is in byte 3 of 'off' for primitive types (buf == + // null) + sb.append((char) ((off & 0xFF000000) >>> 24)); + } else if (sort == OBJECT) { + sb.append('L'); + sb.append(this.buf, off, len); + sb.append(';'); + } else { // sort == ARRAY || sort == METHOD + sb.append(this.buf, off, len); + } + } + + // ------------------------------------------------------------------------ + // Direct conversion from classes to type descriptors, + // without intermediate Type objects + // ------------------------------------------------------------------------ + + /** + * Returns the internal name of the given class. The internal name of a + * class is its fully qualified name, as returned by Class.getName(), where + * '.' are replaced by '/'. + * + * @param c + * an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class<?> c) { + return c.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to the given Java type. + * + * @param c + * an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class<?> c) { + StringBuilder sb = new StringBuilder(); + getDescriptor(sb, c); + return sb.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param c + * a {@link Constructor Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor<?> c) { + Class<?>[] parameters = c.getParameterTypes(); + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(sb, parameters[i]); + } + return sb.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param m + * a {@link Method Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method m) { + Class<?>[] parameters = m.getParameterTypes(); + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(sb, parameters[i]); + } + sb.append(')'); + getDescriptor(sb, m.getReturnType()); + return sb.toString(); + } + + /** + * Appends the descriptor of the given class to the given string builder. + * + * @param sb + * the string buffer to which the descriptor must be appended. + * @param c + * the class whose descriptor must be computed. + */ + private static void getDescriptor(final StringBuilder sb, final Class<?> c) { + Class<?> d = c; + while (true) { + if (d.isPrimitive()) { + char car; + if (d == Integer.TYPE) { + car = 'I'; + } else if (d == Void.TYPE) { + car = 'V'; + } else if (d == Boolean.TYPE) { + car = 'Z'; + } else if (d == Byte.TYPE) { + car = 'B'; + } else if (d == Character.TYPE) { + car = 'C'; + } else if (d == Short.TYPE) { + car = 'S'; + } else if (d == Double.TYPE) { + car = 'D'; + } else if (d == Float.TYPE) { + car = 'F'; + } else /* if (d == Long.TYPE) */{ + car = 'J'; + } + sb.append(car); + return; + } else if (d.isArray()) { + sb.append('['); + d = d.getComponentType(); + } else { + sb.append('L'); + String name = d.getName(); + int len = name.length(); + for (int i = 0; i < len; ++i) { + char car = name.charAt(i); + sb.append(car == '.' ? '/' : car); + } + sb.append(';'); + return; + } + } + } + + // ------------------------------------------------------------------------ + // Corresponding size and opcodes + // ------------------------------------------------------------------------ + + /** + * Returns the size of values of this type. This method must not be used for + * method types. + * + * @return the size of values of this type, i.e., 2 for <tt>long</tt> and + * <tt>double</tt>, 0 for <tt>void</tt> and 1 otherwise. + */ + public int getSize() { + // the size is in byte 0 of 'off' for primitive types (buf == null) + return buf == null ? (off & 0xFF) : 1; + } + + /** + * Returns a JVM instruction opcode adapted to this Java type. This method + * must not be used for method types. + * + * @param opcode + * a JVM instruction opcode. This opcode must be one of ILOAD, + * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, + * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to + * this Java type. For example, if this type is <tt>float</tt> and + * <tt>opcode</tt> is IRETURN, this method returns FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4); + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4); + } + } + + // ------------------------------------------------------------------------ + // Equals, hashCode and toString + // ------------------------------------------------------------------------ + + /** + * Tests if the given object is equal to this type. + * + * @param o + * the object to be compared to this type. + * @return <tt>true</tt> if the given object is equal to this type. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Type)) { + return false; + } + Type t = (Type) o; + if (sort != t.sort) { + return false; + } + if (sort >= ARRAY) { + if (len != t.len) { + return false; + } + for (int i = off, j = t.off, end = i + len; i < end; i++, j++) { + if (buf[i] != t.buf[j]) { + return false; + } + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hc = 13 * sort; + if (sort >= ARRAY) { + for (int i = off, end = i + len; i < end; i++) { + hc = 17 * (hc + buf[i]); + } + } + return hc; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/TypePath.java b/spring-core/src/main/java/org/springframework/asm/TypePath.java new file mode 100644 index 00000000..87df7c2a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/TypePath.java @@ -0,0 +1,196 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2013 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.springframework.asm; + +/** + * The path to a type argument, wildcard bound, array element type, or static + * inner type within an enclosing type. + * + * @author Eric Bruneton + */ +public class TypePath { + + /** + * A type path step that steps into the element type of an array type. See + * {@link #getStep getStep}. + */ + public final static int ARRAY_ELEMENT = 0; + + /** + * A type path step that steps into the nested type of a class type. See + * {@link #getStep getStep}. + */ + public final static int INNER_TYPE = 1; + + /** + * A type path step that steps into the bound of a wildcard type. See + * {@link #getStep getStep}. + */ + public final static int WILDCARD_BOUND = 2; + + /** + * A type path step that steps into a type argument of a generic type. See + * {@link #getStep getStep}. + */ + public final static int TYPE_ARGUMENT = 3; + + /** + * The byte array where the path is stored, in Java class file format. + */ + byte[] b; + + /** + * The offset of the first byte of the type path in 'b'. + */ + int offset; + + /** + * Creates a new type path. + * + * @param b + * the byte array containing the type path in Java class file + * format. + * @param offset + * the offset of the first byte of the type path in 'b'. + */ + TypePath(byte[] b, int offset) { + this.b = b; + this.offset = offset; + } + + /** + * Returns the length of this path. + * + * @return the length of this path. + */ + public int getLength() { + return b[offset]; + } + + /** + * Returns the value of the given step of this path. + * + * @param index + * an index between 0 and {@link #getLength()}, exclusive. + * @return {@link #ARRAY_ELEMENT ARRAY_ELEMENT}, {@link #INNER_TYPE + * INNER_TYPE}, {@link #WILDCARD_BOUND WILDCARD_BOUND}, or + * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}. + */ + public int getStep(int index) { + return b[offset + 2 * index + 1]; + } + + /** + * Returns the index of the type argument that the given step is stepping + * into. This method should only be used for steps whose value is + * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}. + * + * @param index + * an index between 0 and {@link #getLength()}, exclusive. + * @return the index of the type argument that the given step is stepping + * into. + */ + public int getStepArgument(int index) { + return b[offset + 2 * index + 2]; + } + + /** + * Converts a type path in string form, in the format used by + * {@link #toString()}, into a TypePath object. + * + * @param typePath + * a type path in string form, in the format used by + * {@link #toString()}. May be null or empty. + * @return the corresponding TypePath object, or null if the path is empty. + */ + public static TypePath fromString(final String typePath) { + if (typePath == null || typePath.length() == 0) { + return null; + } + int n = typePath.length(); + ByteVector out = new ByteVector(n); + out.putByte(0); + for (int i = 0; i < n;) { + char c = typePath.charAt(i++); + if (c == '[') { + out.put11(ARRAY_ELEMENT, 0); + } else if (c == '.') { + out.put11(INNER_TYPE, 0); + } else if (c == '*') { + out.put11(WILDCARD_BOUND, 0); + } else if (c >= '0' && c <= '9') { + int typeArg = c - '0'; + while (i < n && (c = typePath.charAt(i)) >= '0' && c <= '9') { + typeArg = typeArg * 10 + c - '0'; + i += 1; + } + if (i < n && typePath.charAt(i) == ';') { + i += 1; + } + out.put11(TYPE_ARGUMENT, typeArg); + } + } + out.data[0] = (byte) (out.length / 2); + return new TypePath(out.data, 0); + } + + /** + * Returns a string representation of this type path. {@link #ARRAY_ELEMENT + * ARRAY_ELEMENT} steps are represented with '[', {@link #INNER_TYPE + * INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND WILDCARD_BOUND} steps + * with '*' and {@link #TYPE_ARGUMENT TYPE_ARGUMENT} steps with their type + * argument index in decimal form followed by ';'. + */ + @Override + public String toString() { + int length = getLength(); + StringBuilder result = new StringBuilder(length * 2); + for (int i = 0; i < length; ++i) { + switch (getStep(i)) { + case ARRAY_ELEMENT: + result.append('['); + break; + case INNER_TYPE: + result.append('.'); + break; + case WILDCARD_BOUND: + result.append('*'); + break; + case TYPE_ARGUMENT: + result.append(getStepArgument(i)).append(';'); + break; + default: + result.append('_'); + } + } + return result.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/asm/TypeReference.java b/spring-core/src/main/java/org/springframework/asm/TypeReference.java new file mode 100644 index 00000000..a7149283 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/asm/TypeReference.java @@ -0,0 +1,452 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2013 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.springframework.asm; + +/** + * A reference to a type appearing in a class, field or method declaration, or + * on an instruction. Such a reference designates the part of the class where + * the referenced type is appearing (e.g. an 'extends', 'implements' or 'throws' + * clause, a 'new' instruction, a 'catch' clause, a type cast, a local variable + * declaration, etc). + * + * @author Eric Bruneton + */ +public class TypeReference { + + /** + * The sort of type references that target a type parameter of a generic + * class. See {@link #getSort getSort}. + */ + public final static int CLASS_TYPE_PARAMETER = 0x00; + + /** + * The sort of type references that target a type parameter of a generic + * method. See {@link #getSort getSort}. + */ + public final static int METHOD_TYPE_PARAMETER = 0x01; + + /** + * The sort of type references that target the super class of a class or one + * of the interfaces it implements. See {@link #getSort getSort}. + */ + public final static int CLASS_EXTENDS = 0x10; + + /** + * The sort of type references that target a bound of a type parameter of a + * generic class. See {@link #getSort getSort}. + */ + public final static int CLASS_TYPE_PARAMETER_BOUND = 0x11; + + /** + * The sort of type references that target a bound of a type parameter of a + * generic method. See {@link #getSort getSort}. + */ + public final static int METHOD_TYPE_PARAMETER_BOUND = 0x12; + + /** + * The sort of type references that target the type of a field. See + * {@link #getSort getSort}. + */ + public final static int FIELD = 0x13; + + /** + * The sort of type references that target the return type of a method. See + * {@link #getSort getSort}. + */ + public final static int METHOD_RETURN = 0x14; + + /** + * The sort of type references that target the receiver type of a method. + * See {@link #getSort getSort}. + */ + public final static int METHOD_RECEIVER = 0x15; + + /** + * The sort of type references that target the type of a formal parameter of + * a method. See {@link #getSort getSort}. + */ + public final static int METHOD_FORMAL_PARAMETER = 0x16; + + /** + * The sort of type references that target the type of an exception declared + * in the throws clause of a method. See {@link #getSort getSort}. + */ + public final static int THROWS = 0x17; + + /** + * The sort of type references that target the type of a local variable in a + * method. See {@link #getSort getSort}. + */ + public final static int LOCAL_VARIABLE = 0x40; + + /** + * The sort of type references that target the type of a resource variable + * in a method. See {@link #getSort getSort}. + */ + public final static int RESOURCE_VARIABLE = 0x41; + + /** + * The sort of type references that target the type of the exception of a + * 'catch' clause in a method. See {@link #getSort getSort}. + */ + public final static int EXCEPTION_PARAMETER = 0x42; + + /** + * The sort of type references that target the type declared in an + * 'instanceof' instruction. See {@link #getSort getSort}. + */ + public final static int INSTANCEOF = 0x43; + + /** + * The sort of type references that target the type of the object created by + * a 'new' instruction. See {@link #getSort getSort}. + */ + public final static int NEW = 0x44; + + /** + * The sort of type references that target the receiver type of a + * constructor reference. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_REFERENCE = 0x45; + + /** + * The sort of type references that target the receiver type of a method + * reference. See {@link #getSort getSort}. + */ + public final static int METHOD_REFERENCE = 0x46; + + /** + * The sort of type references that target the type declared in an explicit + * or implicit cast instruction. See {@link #getSort getSort}. + */ + public final static int CAST = 0x47; + + /** + * The sort of type references that target a type parameter of a generic + * constructor in a constructor call. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + + /** + * The sort of type references that target a type parameter of a generic + * method in a method call. See {@link #getSort getSort}. + */ + public final static int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + + /** + * The sort of type references that target a type parameter of a generic + * constructor in a constructor reference. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + + /** + * The sort of type references that target a type parameter of a generic + * method in a method reference. See {@link #getSort getSort}. + */ + public final static int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + /** + * The type reference value in Java class file format. + */ + private int value; + + /** + * Creates a new TypeReference. + * + * @param typeRef + * the int encoded value of the type reference, as received in a + * visit method related to type annotations, like + * visitTypeAnnotation. + */ + public TypeReference(int typeRef) { + this.value = typeRef; + } + + /** + * Returns a type reference of the given sort. + * + * @param sort + * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN}, + * {@link #METHOD_RECEIVER METHOD_RECEIVER}, + * {@link #LOCAL_VARIABLE LOCAL_VARIABLE}, + * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE}, + * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW}, + * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, or + * {@link #METHOD_REFERENCE METHOD_REFERENCE}. + * @return a type reference of the given sort. + */ + public static TypeReference newTypeReference(int sort) { + return new TypeReference(sort << 24); + } + + /** + * Returns a reference to a type parameter of a generic class or method. + * + * @param sort + * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}. + * @param paramIndex + * the type parameter index. + * @return a reference to the given generic class or method type parameter. + */ + public static TypeReference newTypeParameterReference(int sort, + int paramIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to a type parameter bound of a generic class or + * method. + * + * @param sort + * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}. + * @param paramIndex + * the type parameter index. + * @param boundIndex + * the type bound index within the above type parameters. + * @return a reference to the given generic class or method type parameter + * bound. + */ + public static TypeReference newTypeParameterBoundReference(int sort, + int paramIndex, int boundIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16) + | (boundIndex << 8)); + } + + /** + * Returns a reference to the super class or to an interface of the + * 'implements' clause of a class. + * + * @param itfIndex + * the index of an interface in the 'implements' clause of a + * class, or -1 to reference the super class of the class. + * @return a reference to the given super type of a class. + */ + public static TypeReference newSuperTypeReference(int itfIndex) { + itfIndex &= 0xFFFF; + return new TypeReference((CLASS_EXTENDS << 24) | (itfIndex << 8)); + } + + /** + * Returns a reference to the type of a formal parameter of a method. + * + * @param paramIndex + * the formal parameter index. + * + * @return a reference to the type of the given method formal parameter. + */ + public static TypeReference newFormalParameterReference(int paramIndex) { + return new TypeReference((METHOD_FORMAL_PARAMETER << 24) + | (paramIndex << 16)); + } + + /** + * Returns a reference to the type of an exception, in a 'throws' clause of + * a method. + * + * @param exceptionIndex + * the index of an exception in a 'throws' clause of a method. + * + * @return a reference to the type of the given exception. + */ + public static TypeReference newExceptionReference(int exceptionIndex) { + return new TypeReference((THROWS << 24) | (exceptionIndex << 8)); + } + + /** + * Returns a reference to the type of the exception declared in a 'catch' + * clause of a method. + * + * @param tryCatchBlockIndex + * the index of a try catch block (using the order in which they + * are visited with visitTryCatchBlock). + * + * @return a reference to the type of the given exception. + */ + public static TypeReference newTryCatchReference(int tryCatchBlockIndex) { + return new TypeReference((EXCEPTION_PARAMETER << 24) + | (tryCatchBlockIndex << 8)); + } + + /** + * Returns a reference to the type of a type argument in a constructor or + * method call or reference. + * + * @param sort + * {@link #CAST CAST}, + * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. + * @param argIndex + * the type argument index. + * + * @return a reference to the type of the given type argument. + */ + public static TypeReference newTypeArgumentReference(int sort, int argIndex) { + return new TypeReference((sort << 24) | argIndex); + } + + /** + * Returns the sort of this type reference. + * + * @return {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}, + * {@link #CLASS_EXTENDS CLASS_EXTENDS}, + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND}, + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}, + * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN}, + * {@link #METHOD_RECEIVER METHOD_RECEIVER}, + * {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}, + * {@link #THROWS THROWS}, {@link #LOCAL_VARIABLE LOCAL_VARIABLE}, + * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE}, + * {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER}, + * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW}, + * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, + * {@link #METHOD_REFERENCE METHOD_REFERENCE}, {@link #CAST CAST}, + * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. + */ + public int getSort() { + return value >>> 24; + } + + /** + * Returns the index of the type parameter referenced by this type + * reference. This method must only be used for type references whose sort + * is {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}, + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter index. + */ + public int getTypeParameterIndex() { + return (value & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the type parameter bound, within the type parameter + * {@link #getTypeParameterIndex}, referenced by this type reference. This + * method must only be used for type references whose sort is + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter bound index. + */ + public int getTypeParameterBoundIndex() { + return (value & 0x0000FF00) >> 8; + } + + /** + * Returns the index of the "super type" of a class that is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #CLASS_EXTENDS CLASS_EXTENDS}. + * + * @return the index of an interface in the 'implements' clause of a class, + * or -1 if this type reference references the type of the super + * class. + */ + public int getSuperTypeIndex() { + return (short) ((value & 0x00FFFF00) >> 8); + } + + /** + * Returns the index of the formal parameter whose type is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}. + * + * @return a formal parameter index. + */ + public int getFormalParameterIndex() { + return (value & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the exception, in a 'throws' clause of a method, + * whose type is referenced by this type reference. This method must only be + * used for type references whose sort is {@link #THROWS THROWS}. + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getExceptionIndex() { + return (value & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the try catch block (using the order in which they + * are visited with visitTryCatchBlock), whose 'catch' type is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER} . + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getTryCatchBlockIndex() { + return (value & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the type argument referenced by this type reference. + * This method must only be used for type references whose sort is + * {@link #CAST CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT METHOD_REFERENCE_TYPE_ARGUMENT}. + * + * @return a type parameter index. + */ + public int getTypeArgumentIndex() { + return value & 0xFF; + } + + /** + * Returns the int encoded value of this type reference, suitable for use in + * visit methods related to type annotations, like visitTypeAnnotation. + * + * @return the int encoded value of this type reference. + */ + public int getValue() { + return value; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java b/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java index 0ed64ca5..6922898b 100644 --- a/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java +++ b/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,10 @@ package org.springframework.cglib; /** - * Empty class used to ensure that the {@code org.springframework.cglib} package is - * processed during Javadoc generation. + * Empty class used to ensure that the {@code org.springframework.cglib} + * package is processed during javadoc generation. * - * <p>See <a href="package-summary.html">package-level Javadoc</a> for more + * <p>See <a href="package-summary.html">package-level javadocs</a> for more * information on {@code org.springframework.cglib}. * * @author Chris Beams diff --git a/spring-core/src/main/java/org/springframework/cglib/package-info.java b/spring-core/src/main/java/org/springframework/cglib/package-info.java index a7f25ce2..6246aae8 100644 --- a/spring-core/src/main/java/org/springframework/cglib/package-info.java +++ b/spring-core/src/main/java/org/springframework/cglib/package-info.java @@ -1,28 +1,16 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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. - */ /** - * Spring's repackaging of <a href="http://cglib.sourceforge.net">net.sf.cglib 3</a> (for - * internal use only). + * Spring's repackaging of + * <a href="http://cglib.sourceforge.net">net.sf.cglib 3.1</a> + * (for internal use only). + * * <p>This repackaging technique avoids any potential conflicts with - * dependencies on CGLIB at the application level or from other third-party + * dependencies on CGLIB at the application level or from third-party * libraries and frameworks. - * <p>As this repackaging happens at the classfile level, sources and Javadoc - * are not available here. See the original - * <a href="http://cglib.sourceforge.net/apidocs">CGLIB 3 Javadoc</a> + * + * <p>As this repackaging happens at the class file level, sources + * and javadocs are not available here. See the original + * <a href="http://cglib.sourceforge.net/apidocs">CGLIB 3.1 javadocs</a> * for details when working with these classes. * * @since 3.2 diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java index 28f92352..decc7640 100644 --- a/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java @@ -30,7 +30,10 @@ import org.springframework.cglib.transform.TransformingClassGenerator; * * @author Phillip Webb * @since 3.2.4 + * @deprecated as of Spring 4.0.2, in favor of CGLIB 3.1's default strategy. + * Kept around for external code depending on it; to be removed in Spring 4.1. */ +@Deprecated public class MemorySafeUndeclaredThrowableStrategy extends DefaultGeneratorStrategy { private static final MethodFilter TRANSFORM_FILTER = new MethodFilter() { diff --git a/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java b/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java index cf351f42..dbbe1aff 100644 --- a/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java +++ b/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java @@ -39,6 +39,7 @@ public abstract class AttributeAccessorSupport implements AttributeAccessor, Ser private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(0); + @Override public void setAttribute(String name, Object value) { Assert.notNull(name, "Name must not be null"); if (value != null) { @@ -49,21 +50,25 @@ public abstract class AttributeAccessorSupport implements AttributeAccessor, Ser } } + @Override public Object getAttribute(String name) { Assert.notNull(name, "Name must not be null"); return this.attributes.get(name); } + @Override public Object removeAttribute(String name) { Assert.notNull(name, "Name must not be null"); return this.attributes.remove(name); } + @Override public boolean hasAttribute(String name) { Assert.notNull(name, "Name must not be null"); return this.attributes.containsKey(name); } + @Override public String[] attributeNames() { return this.attributes.keySet().toArray(new String[this.attributes.size()]); } diff --git a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java index 62c33d1a..2f44c305 100644 --- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -16,14 +16,11 @@ package org.springframework.core; -import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -44,6 +41,7 @@ import org.springframework.util.ReflectionUtils; * * @author Rob Harrop * @author Juergen Hoeller + * @author Phillip Webb * @since 2.0 */ public abstract class BridgeMethodResolver { @@ -87,6 +85,18 @@ public abstract class BridgeMethodResolver { } /** + * Returns {@code true} if the supplied '{@code candidateMethod}' can be + * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged} + * by the supplied {@link Method bridge Method}. This method performs inexpensive + * checks and can be used quickly filter for a set of possible matches. + */ + private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) { + return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) && + candidateMethod.getName().equals(bridgeMethod.getName()) && + candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length); + } + + /** * Searches for the bridged method in the given candidates. * @param candidateMethods the List of candidate Methods * @param bridgeMethod the bridge method @@ -96,11 +106,10 @@ public abstract class BridgeMethodResolver { if (candidateMethods.isEmpty()) { return null; } - Map<TypeVariable, Type> typeParameterMap = GenericTypeResolver.getTypeVariableMap(bridgeMethod.getDeclaringClass()); Method previousMethod = null; boolean sameSig = true; for (Method candidateMethod : candidateMethods) { - if (isBridgeMethodFor(bridgeMethod, candidateMethod, typeParameterMap)) { + if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) { return candidateMethod; } else if (previousMethod != null) { @@ -113,27 +122,15 @@ public abstract class BridgeMethodResolver { } /** - * Returns {@code true} if the supplied '{@code candidateMethod}' can be - * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged} - * by the supplied {@link Method bridge Method}. This method performs inexpensive - * checks and can be used quickly filter for a set of possible matches. - */ - private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) { - return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) && - candidateMethod.getName().equals(bridgeMethod.getName()) && - candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length); - } - - /** * Determines whether or not the bridge {@link Method} is the bridge for the * supplied candidate {@link Method}. */ - static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) { - if (isResolvedTypeMatch(candidateMethod, bridgeMethod, typeVariableMap)) { + static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) { + if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) { return true; } Method method = findGenericDeclaration(bridgeMethod); - return (method != null && isResolvedTypeMatch(method, candidateMethod, typeVariableMap)); + return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass)); } /** @@ -167,34 +164,27 @@ public abstract class BridgeMethodResolver { /** * Returns {@code true} if the {@link Type} signature of both the supplied * {@link Method#getGenericParameterTypes() generic Method} and concrete {@link Method} - * are equal after resolving all {@link TypeVariable TypeVariables} using the supplied - * TypeVariable Map, otherwise returns {@code false}. + * are equal after resolving all types against the declaringType, otherwise + * returns {@code false}. */ private static boolean isResolvedTypeMatch( - Method genericMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) { - + Method genericMethod, Method candidateMethod, Class<?> declaringClass) { Type[] genericParameters = genericMethod.getGenericParameterTypes(); Class<?>[] candidateParameters = candidateMethod.getParameterTypes(); if (genericParameters.length != candidateParameters.length) { return false; } - for (int i = 0; i < genericParameters.length; i++) { - Type genericParameter = genericParameters[i]; + for (int i = 0; i < candidateParameters.length; i++) { + ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass); Class<?> candidateParameter = candidateParameters[i]; if (candidateParameter.isArray()) { // An array type: compare the component type. - Type rawType = GenericTypeResolver.getRawType(genericParameter, typeVariableMap); - if (rawType instanceof GenericArrayType) { - if (!candidateParameter.getComponentType().equals( - GenericTypeResolver.resolveType(((GenericArrayType) rawType).getGenericComponentType(), typeVariableMap))) { - return false; - } - break; + if (!candidateParameter.getComponentType().equals(genericParameter.getComponentType().resolve(Object.class))) { + return false; } } // A non-array type: compare the type itself. - Class<?> resolvedParameter = GenericTypeResolver.resolveType(genericParameter, typeVariableMap); - if (!candidateParameter.equals(resolvedParameter)) { + if (!candidateParameter.equals(genericParameter.resolve(Object.class))) { return false; } } diff --git a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java index 23335434..998bd813 100644 --- a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java +++ b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,22 +20,19 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; -import org.springframework.util.ClassUtils; -import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -53,13 +50,9 @@ import org.springframework.util.MultiValueMap; */ public abstract class CollectionFactory { - private static Class navigableSetClass = null; + private static final Set<Class<?>> approximableCollectionTypes = new HashSet<Class<?>>(10); - private static Class navigableMapClass = null; - - private static final Set<Class> approximableCollectionTypes = new HashSet<Class>(10); - - private static final Set<Class> approximableMapTypes = new HashSet<Class>(6); + private static final Set<Class<?>> approximableMapTypes = new HashSet<Class<?>>(6); static { @@ -68,20 +61,10 @@ public abstract class CollectionFactory { approximableCollectionTypes.add(List.class); approximableCollectionTypes.add(Set.class); approximableCollectionTypes.add(SortedSet.class); + approximableCollectionTypes.add(NavigableSet.class); approximableMapTypes.add(Map.class); approximableMapTypes.add(SortedMap.class); - - // New Java 6 collection interfaces - ClassLoader cl = CollectionFactory.class.getClassLoader(); - try { - navigableSetClass = ClassUtils.forName("java.util.NavigableSet", cl); - navigableMapClass = ClassUtils.forName("java.util.NavigableMap", cl); - approximableCollectionTypes.add(navigableSetClass); - approximableMapTypes.add(navigableMapClass); - } - catch (ClassNotFoundException ex) { - // not running on Java 6 or above... - } + approximableMapTypes.add(NavigableMap.class); // Common concrete collection classes approximableCollectionTypes.add(ArrayList.class); @@ -96,95 +79,6 @@ public abstract class CollectionFactory { /** - * Create a linked Set if possible: This implementation always - * creates a {@link java.util.LinkedHashSet}, since Spring 2.5 - * requires JDK 1.4 anyway. - * @param initialCapacity the initial capacity of the Set - * @return the new Set instance - * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher - */ - @Deprecated - public static <T> Set<T> createLinkedSetIfPossible(int initialCapacity) { - return new LinkedHashSet<T>(initialCapacity); - } - - /** - * Create a copy-on-write Set (allowing for synchronization-less iteration) if possible: - * This implementation always creates a {@link java.util.concurrent.CopyOnWriteArraySet}, - * since Spring 3 requires JDK 1.5 anyway. - * @return the new Set instance - * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher - */ - @Deprecated - public static <T> Set<T> createCopyOnWriteSet() { - return new CopyOnWriteArraySet<T>(); - } - - /** - * Create a linked Map if possible: This implementation always - * creates a {@link java.util.LinkedHashMap}, since Spring 2.5 - * requires JDK 1.4 anyway. - * @param initialCapacity the initial capacity of the Map - * @return the new Map instance - * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher - */ - @Deprecated - public static <K,V> Map<K,V> createLinkedMapIfPossible(int initialCapacity) { - return new LinkedHashMap<K,V>(initialCapacity); - } - - /** - * Create a linked case-insensitive Map if possible: This implementation - * always returns a {@link org.springframework.util.LinkedCaseInsensitiveMap}. - * @param initialCapacity the initial capacity of the Map - * @return the new Map instance - * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher - */ - @Deprecated - public static Map createLinkedCaseInsensitiveMapIfPossible(int initialCapacity) { - return new LinkedCaseInsensitiveMap(initialCapacity); - } - - /** - * Create an identity Map if possible: This implementation always - * creates a {@link java.util.IdentityHashMap}, since Spring 2.5 - * requires JDK 1.4 anyway. - * @param initialCapacity the initial capacity of the Map - * @return the new Map instance - * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher - */ - @Deprecated - public static Map createIdentityMapIfPossible(int initialCapacity) { - return new IdentityHashMap(initialCapacity); - } - - /** - * Create a concurrent Map if possible: This implementation always - * creates a {@link java.util.concurrent.ConcurrentHashMap}, since Spring 3.0 - * requires JDK 1.5 anyway. - * @param initialCapacity the initial capacity of the Map - * @return the new Map instance - * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher - */ - @Deprecated - public static Map createConcurrentMapIfPossible(int initialCapacity) { - return new ConcurrentHashMap(initialCapacity); - } - - /** - * Create a concurrent Map with a dedicated {@link ConcurrentMap} interface: - * This implementation always creates a {@link java.util.concurrent.ConcurrentHashMap}, - * since Spring 3.0 requires JDK 1.5 anyway. - * @param initialCapacity the initial capacity of the Map - * @return the new ConcurrentMap instance - * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher - */ - @Deprecated - public static ConcurrentMap createConcurrentMap(int initialCapacity) { - return new JdkConcurrentHashMap(initialCapacity); - } - - /** * Determine whether the given collection type is an approximable type, * i.e. a type that {@link #createApproximateCollection} can approximate. * @param collectionType the collection type to check @@ -207,18 +101,18 @@ public abstract class CollectionFactory { * @see java.util.LinkedHashSet */ @SuppressWarnings("unchecked") - public static Collection createApproximateCollection(Object collection, int initialCapacity) { + public static <E> Collection<E> createApproximateCollection(Object collection, int initialCapacity) { if (collection instanceof LinkedList) { - return new LinkedList(); + return new LinkedList<E>(); } else if (collection instanceof List) { - return new ArrayList(initialCapacity); + return new ArrayList<E>(initialCapacity); } else if (collection instanceof SortedSet) { - return new TreeSet(((SortedSet) collection).comparator()); + return new TreeSet<E>(((SortedSet<E>) collection).comparator()); } else { - return new LinkedHashSet(initialCapacity); + return new LinkedHashSet<E>(initialCapacity); } } @@ -233,16 +127,17 @@ public abstract class CollectionFactory { * @see java.util.TreeSet * @see java.util.LinkedHashSet */ - public static Collection createCollection(Class<?> collectionType, int initialCapacity) { + @SuppressWarnings("unchecked") + public static <E> Collection<E> createCollection(Class<?> collectionType, int initialCapacity) { if (collectionType.isInterface()) { if (List.class.equals(collectionType)) { - return new ArrayList(initialCapacity); + return new ArrayList<E>(initialCapacity); } - else if (SortedSet.class.equals(collectionType) || collectionType.equals(navigableSetClass)) { - return new TreeSet(); + else if (SortedSet.class.equals(collectionType) || NavigableSet.class.equals(collectionType)) { + return new TreeSet<E>(); } else if (Set.class.equals(collectionType) || Collection.class.equals(collectionType)) { - return new LinkedHashSet(initialCapacity); + return new LinkedHashSet<E>(initialCapacity); } else { throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName()); @@ -253,7 +148,7 @@ public abstract class CollectionFactory { throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName()); } try { - return (Collection) collectionType.newInstance(); + return (Collection<E>) collectionType.newInstance(); } catch (Exception ex) { throw new IllegalArgumentException("Could not instantiate Collection type: " + @@ -283,12 +178,12 @@ public abstract class CollectionFactory { * @see java.util.LinkedHashMap */ @SuppressWarnings("unchecked") - public static Map createApproximateMap(Object map, int initialCapacity) { + public static <K, V> Map<K, V> createApproximateMap(Object map, int initialCapacity) { if (map instanceof SortedMap) { - return new TreeMap(((SortedMap) map).comparator()); + return new TreeMap<K, V>(((SortedMap<K, V>) map).comparator()); } else { - return new LinkedHashMap(initialCapacity); + return new LinkedHashMap<K, V>(initialCapacity); } } @@ -301,13 +196,14 @@ public abstract class CollectionFactory { * @see java.util.TreeMap * @see java.util.LinkedHashMap */ - public static Map createMap(Class<?> mapType, int initialCapacity) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static <K, V> Map<K, V> createMap(Class<?> mapType, int initialCapacity) { if (mapType.isInterface()) { if (Map.class.equals(mapType)) { - return new LinkedHashMap(initialCapacity); + return new LinkedHashMap<K, V>(initialCapacity); } - else if (SortedMap.class.equals(mapType) || mapType.equals(navigableMapClass)) { - return new TreeMap(); + else if (SortedMap.class.equals(mapType) || NavigableMap.class.equals(mapType)) { + return new TreeMap<K, V>(); } else if (MultiValueMap.class.equals(mapType)) { return new LinkedMultiValueMap(); @@ -321,7 +217,7 @@ public abstract class CollectionFactory { throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName()); } try { - return (Map) mapType.newInstance(); + return (Map<K, V>) mapType.newInstance(); } catch (Exception ex) { throw new IllegalArgumentException("Could not instantiate Map type: " + @@ -330,17 +226,4 @@ public abstract class CollectionFactory { } } - - /** - * ConcurrentMap adapter for the JDK ConcurrentHashMap class. - */ - @Deprecated - @SuppressWarnings("serial") - private static class JdkConcurrentHashMap extends ConcurrentHashMap implements ConcurrentMap { - - private JdkConcurrentHashMap(int initialCapacity) { - super(initialCapacity); - } - } - } diff --git a/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java b/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java deleted file mode 100644 index 64597193..00000000 --- a/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2009 the original author or authors. - * - * 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 org.springframework.core; - -import java.util.Map; - -/** - * Common interface for a concurrent Map, as exposed by - * {@link CollectionFactory#createConcurrentMap}. Mirrors - * {@link java.util.concurrent.ConcurrentMap}, allowing to be backed by a - * JDK ConcurrentHashMap as well as a backport-concurrent ConcurrentHashMap. - * - * <p>Check out the {@link java.util.concurrent.ConcurrentMap ConcurrentMap javadoc} - * for details on the interface's methods. - * - * @author Juergen Hoeller - * @since 2.5 - * @deprecated as of Spring 3.0, since standard {@link java.util.concurrent.ConcurrentMap} - * is available on Java 5+ anyway - */ -@Deprecated -public interface ConcurrentMap extends Map { - - Object putIfAbsent(Object key, Object value); - - boolean remove(Object key, Object value); - - boolean replace(Object key, Object oldValue, Object newValue); - - Object replace(Object key, Object value); - -} diff --git a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java index 4d48b044..5af282f6 100644 --- a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java +++ b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java @@ -68,7 +68,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream { @Override - protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException { + protected Class<?> resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException { try { if (this.classLoader != null) { // Use the specified ClassLoader to resolve local classes. @@ -85,13 +85,13 @@ public class ConfigurableObjectInputStream extends ObjectInputStream { } @Override - protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { + protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { if (!this.acceptProxyClasses) { throw new NotSerializableException("Not allowed to accept serialized proxy classes"); } if (this.classLoader != null) { // Use the specified ClassLoader to resolve local proxy classes. - Class[] resolvedInterfaces = new Class[interfaces.length]; + Class<?>[] resolvedInterfaces = new Class<?>[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { try { resolvedInterfaces[i] = ClassUtils.forName(interfaces[i], this.classLoader); @@ -113,7 +113,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream { return super.resolveProxyClass(interfaces); } catch (ClassNotFoundException ex) { - Class[] resolvedInterfaces = new Class[interfaces.length]; + Class<?>[] resolvedInterfaces = new Class<?>[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex); } @@ -131,7 +131,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream { * @param ex the original exception thrown when attempting to load the class * @return the newly resolved class (never {@code null}) */ - protected Class resolveFallbackIfPossible(String className, ClassNotFoundException ex) + protected Class<?> resolveFallbackIfPossible(String className, ClassNotFoundException ex) throws IOException, ClassNotFoundException{ throw ex; diff --git a/spring-core/src/main/java/org/springframework/core/ControlFlow.java b/spring-core/src/main/java/org/springframework/core/ControlFlow.java index bc754211..a650c6cb 100644 --- a/spring-core/src/main/java/org/springframework/core/ControlFlow.java +++ b/spring-core/src/main/java/org/springframework/core/ControlFlow.java @@ -31,7 +31,7 @@ public interface ControlFlow { * according to the current stack trace. * @param clazz the clazz to look for */ - boolean under(Class clazz); + boolean under(Class<?> clazz); /** * Detect whether we're under the given class and method, @@ -39,7 +39,7 @@ public interface ControlFlow { * @param clazz the clazz to look for * @param methodName the name of the method to look for */ - boolean under(Class clazz, String methodName); + boolean under(Class<?> clazz, String methodName); /** * Detect whether the current stack trace contains the given token. diff --git a/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java b/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java index 6430ff7f..167de0f7 100644 --- a/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java +++ b/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,8 @@ public abstract class ControlFlowFactory { /** * Searches for class name match in a StackTraceElement. */ - public boolean under(Class clazz) { + @Override + public boolean under(Class<?> clazz) { Assert.notNull(clazz, "Class must not be null"); String className = clazz.getName(); for (int i = 0; i < stack.length; i++) { @@ -76,7 +77,8 @@ public abstract class ControlFlowFactory { * Searches for class name match plus method name match * in a StackTraceElement. */ - public boolean under(Class clazz, String methodName) { + @Override + public boolean under(Class<?> clazz, String methodName) { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(methodName, "Method name must not be null"); String className = clazz.getName(); @@ -93,6 +95,7 @@ public abstract class ControlFlowFactory { * Leave it up to the caller to decide what matches. * Caller must understand stack trace format, so there's less abstraction. */ + @Override public boolean underToken(String token) { if (token == null) { return false; diff --git a/spring-core/src/main/java/org/springframework/core/Conventions.java b/spring-core/src/main/java/org/springframework/core/Conventions.java index 7ea7c825..99e3406f 100644 --- a/spring-core/src/main/java/org/springframework/core/Conventions.java +++ b/spring-core/src/main/java/org/springframework/core/Conventions.java @@ -20,7 +20,9 @@ import java.io.Externalizable; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -43,18 +45,18 @@ public abstract class Conventions { */ private static final String PLURAL_SUFFIX = "List"; - /** * Set of interfaces that are supposed to be ignored * when searching for the 'primary' interface of a proxy. */ - private static final Set<Class> ignoredInterfaces = new HashSet<Class>(); - + private static final Set<Class<?>> IGNORED_INTERFACES; static { - ignoredInterfaces.add(Serializable.class); - ignoredInterfaces.add(Externalizable.class); - ignoredInterfaces.add(Cloneable.class); - ignoredInterfaces.add(Comparable.class); + IGNORED_INTERFACES = Collections.unmodifiableSet( + new HashSet<Class<?>>(Arrays.<Class<?>> asList( + Serializable.class, + Externalizable.class, + Cloneable.class, + Comparable.class))); } @@ -75,7 +77,7 @@ public abstract class Conventions { */ public static String getVariableName(Object value) { Assert.notNull(value, "Value must not be null"); - Class valueClass; + Class<?> valueClass; boolean pluralize = false; if (value.getClass().isArray()) { @@ -83,7 +85,7 @@ public abstract class Conventions { pluralize = true; } else if (value instanceof Collection) { - Collection collection = (Collection) value; + Collection<?> collection = (Collection<?>) value; if (collection.isEmpty()) { throw new IllegalArgumentException("Cannot generate variable name for an empty Collection"); } @@ -107,7 +109,7 @@ public abstract class Conventions { */ public static String getVariableNameForParameter(MethodParameter parameter) { Assert.notNull(parameter, "MethodParameter must not be null"); - Class valueClass; + Class<?> valueClass; boolean pluralize = false; if (parameter.getParameterType().isArray()) { @@ -163,7 +165,7 @@ public abstract class Conventions { * @param value the return value (may be {@code null} if not available) * @return the generated variable name */ - public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) { + public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, Object value) { Assert.notNull(method, "Method must not be null"); if (Object.class.equals(resolvedType)) { @@ -173,7 +175,7 @@ public abstract class Conventions { return getVariableName(value); } - Class valueClass; + Class<?> valueClass; boolean pluralize = false; if (resolvedType.isArray()) { @@ -187,7 +189,7 @@ public abstract class Conventions { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection return type and a non-Collection value"); } - Collection collection = (Collection) value; + Collection<?> collection = (Collection<?>) value; if (collection.isEmpty()) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection return type and an empty Collection value"); @@ -239,7 +241,7 @@ public abstract class Conventions { * the attribute name '{@code foo}' qualified by {@link Class} '{@code com.myapp.SomeClass}' * would be '{@code com.myapp.SomeClass.foo}' */ - public static String getQualifiedAttributeName(Class enclosingClass, String attributeName) { + public static String getQualifiedAttributeName(Class<?> enclosingClass, String attributeName) { Assert.notNull(enclosingClass, "'enclosingClass' must not be null"); Assert.notNull(attributeName, "'attributeName' must not be null"); return enclosingClass.getName() + "." + attributeName; @@ -255,12 +257,12 @@ public abstract class Conventions { * @param value the value to check * @return the class to use for naming a variable */ - private static Class getClassForValue(Object value) { - Class valueClass = value.getClass(); + private static Class<?> getClassForValue(Object value) { + Class<?> valueClass = value.getClass(); if (Proxy.isProxyClass(valueClass)) { - Class[] ifcs = valueClass.getInterfaces(); - for (Class ifc : ifcs) { - if (!ignoredInterfaces.contains(ifc)) { + Class<?>[] ifcs = valueClass.getInterfaces(); + for (Class<?> ifc : ifcs) { + if (!IGNORED_INTERFACES.contains(ifc)) { return ifc; } } @@ -285,13 +287,13 @@ public abstract class Conventions { * The exact element for which the {@code Class} is retreived will depend * on the concrete {@code Collection} implementation. */ - private static Object peekAhead(Collection collection) { - Iterator it = collection.iterator(); + private static <E> E peekAhead(Collection<E> collection) { + Iterator<E> it = collection.iterator(); if (!it.hasNext()) { throw new IllegalStateException( "Unable to peek ahead in non-empty collection - no element found"); } - Object value = it.next(); + E value = it.next(); if (value == null) { throw new IllegalStateException( "Unable to peek ahead in non-empty collection - only null element found"); diff --git a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java new file mode 100644 index 00000000..3bb8759a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core; + +/** + * Default implementation of the {@link ParameterNameDiscoverer} strategy interface, + * using the Java 8 standard reflection mechanism (if available), and falling back + * to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking + * debug information in the class file. + * + * <p>Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}. + * + * @author Juergen Hoeller + * @since 4.0 + * @see StandardReflectionParameterNameDiscoverer + * @see LocalVariableTableParameterNameDiscoverer + */ +public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { + + private static final boolean standardReflectionAvailable = + (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_18); + + + public DefaultParameterNameDiscoverer() { + if (standardReflectionAvailable) { + addDiscoverer(new StandardReflectionParameterNameDiscoverer()); + } + addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java b/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java index 53e3425f..ca014d89 100644 --- a/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java +++ b/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,13 +55,14 @@ public class ExceptionDepthComparator implements Comparator<Class<? extends Thro } + @Override public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) { int depth1 = getDepth(o1, this.targetException, 0); int depth2 = getDepth(o2, this.targetException, 0); return (depth1 - depth2); } - private int getDepth(Class declaredException, Class exceptionToMatch, int depth) { + private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) { if (declaredException.equals(exceptionToMatch)) { // Found it! return depth; diff --git a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java index 8b4c280a..22d2a873 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java @@ -16,15 +16,8 @@ package org.springframework.core; -import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.MalformedParameterizedTypeException; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; import java.util.Collection; import java.util.Map; @@ -36,7 +29,9 @@ import java.util.Map; * (to be able to attempt type conversion if appropriate). * * @author Juergen Hoeller + * @author Phillip Webb * @since 2.0 + * @see ResolvableType */ public abstract class GenericCollectionTypeResolver { @@ -46,8 +41,9 @@ public abstract class GenericCollectionTypeResolver { * @param collectionClass the collection class to introspect * @return the generic type, or {@code null} if none */ + @SuppressWarnings("rawtypes") public static Class<?> getCollectionType(Class<? extends Collection> collectionClass) { - return extractTypeFromClass(collectionClass, Collection.class, 0); + return ResolvableType.forClass(collectionClass).asCollection().resolveGeneric(); } /** @@ -56,8 +52,9 @@ public abstract class GenericCollectionTypeResolver { * @param mapClass the map class to introspect * @return the generic type, or {@code null} if none */ + @SuppressWarnings("rawtypes") public static Class<?> getMapKeyType(Class<? extends Map> mapClass) { - return extractTypeFromClass(mapClass, Map.class, 0); + return ResolvableType.forClass(mapClass).asMap().resolveGeneric(0); } /** @@ -66,8 +63,9 @@ public abstract class GenericCollectionTypeResolver { * @param mapClass the map class to introspect * @return the generic type, or {@code null} if none */ + @SuppressWarnings("rawtypes") public static Class<?> getMapValueType(Class<? extends Map> mapClass) { - return extractTypeFromClass(mapClass, Map.class, 1); + return ResolvableType.forClass(mapClass).asMap().resolveGeneric(1); } /** @@ -76,7 +74,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getCollectionFieldType(Field collectionField) { - return getGenericFieldType(collectionField, Collection.class, 0, null, 1); + return ResolvableType.forField(collectionField).asCollection().resolveGeneric(); } /** @@ -88,7 +86,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel) { - return getGenericFieldType(collectionField, Collection.class, 0, null, nestingLevel); + return ResolvableType.forField(collectionField).getNested(nestingLevel).asCollection().resolveGeneric(); } /** @@ -100,9 +98,11 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none + * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels */ + @Deprecated public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) { - return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel); + return ResolvableType.forField(collectionField).getNested(nestingLevel, typeIndexesPerLevel).asCollection().resolveGeneric(); } /** @@ -111,7 +111,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapKeyFieldType(Field mapField) { - return getGenericFieldType(mapField, Map.class, 0, null, 1); + return ResolvableType.forField(mapField).asMap().resolveGeneric(0); } /** @@ -123,7 +123,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel) { - return getGenericFieldType(mapField, Map.class, 0, null, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(0); } /** @@ -135,9 +135,11 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none + * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels */ + @Deprecated public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) { - return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(0); } /** @@ -146,7 +148,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapValueFieldType(Field mapField) { - return getGenericFieldType(mapField, Map.class, 1, null, 1); + return ResolvableType.forField(mapField).asMap().resolveGeneric(1); } /** @@ -158,7 +160,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel) { - return getGenericFieldType(mapField, Map.class, 1, null, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(1); } /** @@ -170,9 +172,11 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none + * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels */ + @Deprecated public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) { - return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(1); } /** @@ -181,7 +185,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getCollectionParameterType(MethodParameter methodParam) { - return getGenericParameterType(methodParam, Collection.class, 0); + return ResolvableType.forMethodParameter(methodParam).asCollection().resolveGeneric(); } /** @@ -190,7 +194,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapKeyParameterType(MethodParameter methodParam) { - return getGenericParameterType(methodParam, Map.class, 0); + return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(0); } /** @@ -199,7 +203,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapValueParameterType(MethodParameter methodParam) { - return getGenericParameterType(methodParam, Map.class, 1); + return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(1); } /** @@ -208,7 +212,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getCollectionReturnType(Method method) { - return getGenericReturnType(method, Collection.class, 0, 1); + return ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric(); } /** @@ -222,7 +226,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getCollectionReturnType(Method method, int nestingLevel) { - return getGenericReturnType(method, Collection.class, 0, nestingLevel); + return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asCollection().resolveGeneric(); } /** @@ -231,7 +235,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapKeyReturnType(Method method) { - return getGenericReturnType(method, Map.class, 0, 1); + return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(0); } /** @@ -243,7 +247,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapKeyReturnType(Method method, int nestingLevel) { - return getGenericReturnType(method, Map.class, 0, nestingLevel); + return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(0); } /** @@ -252,7 +256,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapValueReturnType(Method method) { - return getGenericReturnType(method, Map.class, 1, 1); + return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(1); } /** @@ -264,220 +268,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class<?> getMapValueReturnType(Method method, int nestingLevel) { - return getGenericReturnType(method, Map.class, 1, nestingLevel); - } - - - /** - * Extract the generic parameter type from the given method or constructor. - * @param methodParam the method parameter specification - * @param source the source class/interface defining the generic parameter types - * @param typeIndex the index of the type (e.g. 0 for Collections, - * 0 for Map keys, 1 for Map values) - * @return the generic type, or {@code null} if none - */ - private static Class<?> getGenericParameterType(MethodParameter methodParam, Class<?> source, int typeIndex) { - return extractType(GenericTypeResolver.getTargetType(methodParam), source, typeIndex, - methodParam.typeVariableMap, methodParam.typeIndexesPerLevel, methodParam.getNestingLevel(), 1); - } - - /** - * Extract the generic type from the given field. - * @param field the field to check the type for - * @param source the source class/interface defining the generic parameter types - * @param typeIndex the index of the type (e.g. 0 for Collections, - * 0 for Map keys, 1 for Map values) - * @param nestingLevel the nesting level of the target type - * @return the generic type, or {@code null} if none - */ - private static Class<?> getGenericFieldType(Field field, Class<?> source, int typeIndex, - Map<Integer, Integer> typeIndexesPerLevel, int nestingLevel) { - return extractType(field.getGenericType(), source, typeIndex, null, typeIndexesPerLevel, nestingLevel, 1); - } - - /** - * Extract the generic return type from the given method. - * @param method the method to check the return type for - * @param source the source class/interface defining the generic parameter types - * @param typeIndex the index of the type (e.g. 0 for Collections, - * 0 for Map keys, 1 for Map values) - * @param nestingLevel the nesting level of the target type - * @return the generic type, or {@code null} if none - */ - private static Class<?> getGenericReturnType(Method method, Class<?> source, int typeIndex, int nestingLevel) { - return extractType(method.getGenericReturnType(), source, typeIndex, null, null, nestingLevel, 1); - } - - /** - * Extract the generic type from the given Type object. - * @param type the Type to check - * @param source the source collection/map Class that we check - * @param typeIndex the index of the actual type argument - * @param nestingLevel the nesting level of the target type - * @param currentLevel the current nested level - * @return the generic type as Class, or {@code null} if none - */ - private static Class<?> extractType(Type type, Class<?> source, int typeIndex, - Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel, - int nestingLevel, int currentLevel) { - - Type resolvedType = type; - if (type instanceof TypeVariable && typeVariableMap != null) { - Type mappedType = typeVariableMap.get(type); - if (mappedType != null) { - resolvedType = mappedType; - } - } - if (resolvedType instanceof ParameterizedType) { - return extractTypeFromParameterizedType((ParameterizedType) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, - nestingLevel, currentLevel); - } - else if (resolvedType instanceof Class) { - return extractTypeFromClass((Class) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, - nestingLevel, currentLevel); - } - else if (resolvedType instanceof GenericArrayType) { - Type compType = ((GenericArrayType) resolvedType).getGenericComponentType(); - return extractType(compType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel + 1); - } - else { - return null; - } - } - - /** - * Extract the generic type from the given ParameterizedType object. - * @param ptype the ParameterizedType to check - * @param source the expected raw source type (can be {@code null}) - * @param typeIndex the index of the actual type argument - * @param nestingLevel the nesting level of the target type - * @param currentLevel the current nested level - * @return the generic type as Class, or {@code null} if none - */ - private static Class<?> extractTypeFromParameterizedType(ParameterizedType ptype, Class<?> source, int typeIndex, - Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel, - int nestingLevel, int currentLevel) { - - if (!(ptype.getRawType() instanceof Class)) { - return null; - } - Class rawType = (Class) ptype.getRawType(); - Type[] paramTypes = ptype.getActualTypeArguments(); - if (nestingLevel - currentLevel > 0) { - int nextLevel = currentLevel + 1; - Integer currentTypeIndex = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(nextLevel) : null); - // Default is last parameter type: Collection element or Map value. - int indexToUse = (currentTypeIndex != null ? currentTypeIndex : paramTypes.length - 1); - Type paramType = paramTypes[indexToUse]; - return extractType(paramType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, nextLevel); - } - if (source != null && !source.isAssignableFrom(rawType)) { - return null; - } - Class fromSuperclassOrInterface = extractTypeFromClass(rawType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, - nestingLevel, currentLevel); - if (fromSuperclassOrInterface != null) { - return fromSuperclassOrInterface; - } - if (paramTypes == null || typeIndex >= paramTypes.length) { - return null; - } - Type paramType = paramTypes[typeIndex]; - if (paramType instanceof TypeVariable && typeVariableMap != null) { - Type mappedType = typeVariableMap.get(paramType); - if (mappedType != null) { - paramType = mappedType; - } - } - if (paramType instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) paramType; - Type[] upperBounds = wildcardType.getUpperBounds(); - if (upperBounds != null && upperBounds.length > 0 && !Object.class.equals(upperBounds[0])) { - paramType = upperBounds[0]; - } - else { - Type[] lowerBounds = wildcardType.getLowerBounds(); - if (lowerBounds != null && lowerBounds.length > 0 && !Object.class.equals(lowerBounds[0])) { - paramType = lowerBounds[0]; - } - } - } - if (paramType instanceof ParameterizedType) { - paramType = ((ParameterizedType) paramType).getRawType(); - } - if (paramType instanceof GenericArrayType) { - // A generic array type... Let's turn it into a straight array type if possible. - Type compType = ((GenericArrayType) paramType).getGenericComponentType(); - if (compType instanceof Class) { - return Array.newInstance((Class) compType, 0).getClass(); - } - } - else if (paramType instanceof Class) { - // We finally got a straight Class... - return (Class) paramType; - } - return null; - } - - /** - * Extract the generic type from the given Class object. - * @param clazz the Class to check - * @param source the expected raw source type (can be {@code null}) - * @param typeIndex the index of the actual type argument - * @return the generic type as Class, or {@code null} if none - */ - private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex) { - return extractTypeFromClass(clazz, source, typeIndex, null, null, 1, 1); - } - - /** - * Extract the generic type from the given Class object. - * @param clazz the Class to check - * @param source the expected raw source type (can be {@code null}) - * @param typeIndex the index of the actual type argument - * @param nestingLevel the nesting level of the target type - * @param currentLevel the current nested level - * @return the generic type as Class, or {@code null} if none - */ - private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex, - Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel, - int nestingLevel, int currentLevel) { - - if (clazz.getName().startsWith("java.util.")) { - return null; - } - if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) { - try { - return extractType(clazz.getGenericSuperclass(), source, typeIndex, typeVariableMap, - typeIndexesPerLevel, nestingLevel, currentLevel); - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - ignore and continue with interface introspection - } - } - Type[] ifcs = clazz.getGenericInterfaces(); - if (ifcs != null) { - for (Type ifc : ifcs) { - Type rawType = ifc; - if (ifc instanceof ParameterizedType) { - rawType = ((ParameterizedType) ifc).getRawType(); - } - if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) { - return extractType(ifc, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel); - } - } - } - return null; - } - - /** - * Determine whether the given class is a potential candidate - * that defines generic collection or map types. - * @param clazz the class to check - * @return whether the given class is assignable to Collection or Map - */ - private static boolean isIntrospectionCandidate(Class clazz) { - return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)); + return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(1); } } diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index b860aaaf..bcef2720 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -16,14 +16,12 @@ package org.springframework.core; -import java.lang.reflect.Array; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.MalformedParameterizedTypeException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -39,34 +37,28 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @author Juergen Hoeller * @author Rob Harrop * @author Sam Brannen + * @author Phillip Webb * @since 2.5.2 * @see GenericCollectionTypeResolver */ public abstract class GenericTypeResolver { /** Cache from Class to TypeVariable Map */ - private static final Map<Class, Map<TypeVariable, Type>> typeVariableCache = - new ConcurrentReferenceHashMap<Class, Map<TypeVariable,Type>>(); + @SuppressWarnings("rawtypes") + private static final Map<Class<?>, Map<TypeVariable, Type>> typeVariableCache = + new ConcurrentReferenceHashMap<Class<?>, Map<TypeVariable, Type>>(); /** * Determine the target type for the given parameter specification. * @param methodParam the method parameter specification * @return the corresponding generic parameter type + * @deprecated as of Spring 4.0, use {@link MethodParameter#getGenericParameterType()} */ + @Deprecated public static Type getTargetType(MethodParameter methodParam) { Assert.notNull(methodParam, "MethodParameter must not be null"); - if (methodParam.getConstructor() != null) { - return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()]; - } - else { - if (methodParam.getParameterIndex() >= 0) { - return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()]; - } - else { - return methodParam.getMethod().getGenericReturnType(); - } - } + return methodParam.getGenericParameterType(); } /** @@ -76,14 +68,11 @@ public abstract class GenericTypeResolver { * @return the corresponding generic parameter or return type */ public static Class<?> resolveParameterType(MethodParameter methodParam, Class<?> clazz) { - Type genericType = getTargetType(methodParam); + Assert.notNull(methodParam, "MethodParameter must not be null"); Assert.notNull(clazz, "Class must not be null"); - Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz); - Type rawType = getRawType(genericType, typeVariableMap); - Class<?> result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType()); - methodParam.setParameterType(result); - methodParam.typeVariableMap = typeVariableMap; - return result; + methodParam.setContainingClass(clazz); + methodParam.setParameterType(ResolvableType.forMethodParameter(methodParam).resolve()); + return methodParam.getParameterType(); } /** @@ -96,11 +85,8 @@ public abstract class GenericTypeResolver { */ public static Class<?> resolveReturnType(Method method, Class<?> clazz) { Assert.notNull(method, "Method must not be null"); - Type genericType = method.getGenericReturnType(); Assert.notNull(clazz, "Class must not be null"); - Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz); - Type rawType = getRawType(genericType, typeVariableMap); - return (rawType instanceof Class ? (Class<?>) rawType : method.getReturnType()); + return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType()); } /** @@ -112,7 +98,7 @@ public abstract class GenericTypeResolver { * method for {@code creatProxy()} and an {@code Object[]} array containing * {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will * infer that the target return type is {@code MyService}. - * <pre>{@code public static <T> T createProxy(Class<T> clazz)}</pre> + * <pre class="code">{@code public static <T> T createProxy(Class<T> clazz)}</pre> * <h4>Possible Return Values</h4> * <ul> * <li>the target return type, if it can be inferred</li> @@ -128,14 +114,14 @@ public abstract class GenericTypeResolver { * </ul> * @param method the method to introspect, never {@code null} * @param args the arguments that will be supplied to the method when it is - * invoked, never {@code null} + * invoked (never {@code null}) + * @param classLoader the ClassLoader to resolve class names against, if necessary + * (may be {@code null}) * @return the resolved target return type, the standard return type, or {@code null} - * @since 3.2 - * @deprecated in favor of resolveReturnTypeForFactoryMethod in the internal - * AutowireUtils class in the beans module; we do not expect other use of it! + * @since 3.2.5 + * @see #resolveReturnType */ - @Deprecated - public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args) { + public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args, ClassLoader classLoader) { Assert.notNull(method, "Method must not be null"); Assert.notNull(args, "Argument array must not be null"); @@ -175,8 +161,18 @@ public abstract class GenericTypeResolver { Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type typeArg : actualTypeArguments) { if (typeArg.equals(genericReturnType)) { - if (args[i] instanceof Class) { - return (Class<?>) args[i]; + Object arg = args[i]; + if (arg instanceof Class) { + return (Class<?>) arg; + } + else if (arg instanceof String && classLoader != null) { + try { + return classLoader.loadClass((String) arg); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException( + "Could not resolve specific class name argument [" + arg + "]", ex); + } } else { // Consider adding logic to determine the class of the typeArg, if possible. @@ -204,22 +200,11 @@ public abstract class GenericTypeResolver { */ public static Class<?> resolveReturnTypeArgument(Method method, Class<?> genericIfc) { Assert.notNull(method, "method must not be null"); - Type returnType = method.getReturnType(); - Type genericReturnType = method.getGenericReturnType(); - if (returnType.equals(genericIfc)) { - if (genericReturnType instanceof ParameterizedType) { - ParameterizedType targetType = (ParameterizedType) genericReturnType; - Type[] actualTypeArguments = targetType.getActualTypeArguments(); - Type typeArg = actualTypeArguments[0]; - if (!(typeArg instanceof WildcardType)) { - return (Class<?>) typeArg; - } - } - else { - return null; - } + ResolvableType resolvableType = ResolvableType.forMethodReturnType(method).as(genericIfc); + if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) { + return null; } - return resolveTypeArgument((Class<?>) returnType, genericIfc); + return getSingleGeneric(resolvableType); } /** @@ -231,281 +216,114 @@ public abstract class GenericTypeResolver { * @return the resolved type of the argument, or {@code null} if not resolvable */ public static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) { - Class<?>[] typeArgs = resolveTypeArguments(clazz, genericIfc); - if (typeArgs == null) { + ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc); + if (!resolvableType.hasGenerics()) { return null; } - if (typeArgs.length != 1) { + return getSingleGeneric(resolvableType); + } + + private static Class<?> getSingleGeneric(ResolvableType resolvableType) { + if (resolvableType.getGenerics().length > 1) { throw new IllegalArgumentException("Expected 1 type argument on generic interface [" + - genericIfc.getName() + "] but found " + typeArgs.length); + resolvableType + "] but found " + resolvableType.getGenerics().length); } - return typeArgs[0]; + return resolvableType.getGeneric().resolve(); } + /** * Resolve the type arguments of the given generic interface against the given * target class which is assumed to implement the generic interface and possibly * declare concrete types for its type variables. - * <p>Note: In Spring 3.2, this method doesn't return {@code null} in all scenarios - * where it should. To be fixed in Spring 4.0; for client code, this just means it - * might see {@code null} in a few more cases then where it now sees an array with - * a single {@link Object} type. * @param clazz the target class to check against * @param genericIfc the generic interface or superclass to resolve the type argument from * @return the resolved type of each argument, with the array size matching the * number of actual type arguments, or {@code null} if not resolvable */ public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericIfc) { - return doResolveTypeArguments(clazz, clazz, genericIfc); - } - - private static Class<?>[] doResolveTypeArguments(Class<?> ownerClass, Class<?> classToIntrospect, Class<?> genericIfc) { - while (classToIntrospect != null) { - if (genericIfc.isInterface()) { - Type[] ifcs = classToIntrospect.getGenericInterfaces(); - for (Type ifc : ifcs) { - Class<?>[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); - if (result != null) { - return result; - } - } - } - else { - try { - Class<?>[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc); - if (result != null) { - return result; - } - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - return null to skip further superclass traversal - return null; - } - } - classToIntrospect = classToIntrospect.getSuperclass(); - } - return null; - } - - private static Class<?>[] doResolveTypeArguments(Class<?> ownerClass, Type ifc, Class<?> genericIfc) { - if (ifc instanceof ParameterizedType) { - ParameterizedType paramIfc = (ParameterizedType) ifc; - Type rawType = paramIfc.getRawType(); - if (genericIfc.equals(rawType)) { - Type[] typeArgs = paramIfc.getActualTypeArguments(); - Class<?>[] result = new Class[typeArgs.length]; - for (int i = 0; i < typeArgs.length; i++) { - Type arg = typeArgs[i]; - result[i] = extractClass(ownerClass, arg); - } - return result; - } - else if (genericIfc.isAssignableFrom((Class) rawType)) { - return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc); - } - } - else if (ifc != null && genericIfc.isAssignableFrom((Class) ifc)) { - return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc); - } - return null; - } - - /** - * Extract a Class from the given Type. - */ - private static Class<?> extractClass(Class<?> ownerClass, Type arg) { - if (arg instanceof ParameterizedType) { - return extractClass(ownerClass, ((ParameterizedType) arg).getRawType()); - } - else if (arg instanceof GenericArrayType) { - GenericArrayType gat = (GenericArrayType) arg; - Type gt = gat.getGenericComponentType(); - Class<?> componentClass = extractClass(ownerClass, gt); - return Array.newInstance(componentClass, 0).getClass(); - } - else if (arg instanceof TypeVariable) { - TypeVariable tv = (TypeVariable) arg; - arg = getTypeVariableMap(ownerClass).get(tv); - if (arg == null) { - arg = extractBoundForTypeVariable(tv); - if (arg instanceof ParameterizedType) { - return extractClass(ownerClass, ((ParameterizedType) arg).getRawType()); - } - } - else { - return extractClass(ownerClass, arg); - } + ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc); + if (!type.hasGenerics() || type.isEntirelyUnresolvable()) { + return null; } - return (arg instanceof Class ? (Class) arg : Object.class); + return type.resolveGenerics(Object.class); } /** * Resolve the specified generic type against the given TypeVariable map. * @param genericType the generic type to resolve - * @param typeVariableMap the TypeVariable Map to resolved against + * @param map the TypeVariable Map to resolved against * @return the type if it resolves to a Class, or {@code Object.class} otherwise + * @deprecated as of Spring 4.0 in favor of {@link ResolvableType} */ - public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> typeVariableMap) { - Type resolvedType = getRawType(genericType, typeVariableMap); - if (resolvedType instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) resolvedType).getGenericComponentType(); - Class<?> componentClass = resolveType(componentType, typeVariableMap); - resolvedType = Array.newInstance(componentClass, 0).getClass(); - } - return (resolvedType instanceof Class ? (Class) resolvedType : Object.class); - } - - /** - * Determine the raw type for the given generic parameter type. - * @param genericType the generic type to resolve - * @param typeVariableMap the TypeVariable Map to resolved against - * @return the resolved raw type - */ - static Type getRawType(Type genericType, Map<TypeVariable, Type> typeVariableMap) { - Type resolvedType = genericType; - if (genericType instanceof TypeVariable) { - TypeVariable tv = (TypeVariable) genericType; - resolvedType = typeVariableMap.get(tv); - if (resolvedType == null) { - resolvedType = extractBoundForTypeVariable(tv); - } - } - if (resolvedType instanceof ParameterizedType) { - return ((ParameterizedType) resolvedType).getRawType(); - } - else { - return resolvedType; - } + @Deprecated + @SuppressWarnings("rawtypes") + public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> map) { + return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).resolve(Object.class); } /** * Build a mapping of {@link TypeVariable#getName TypeVariable names} to * {@link Class concrete classes} for the specified {@link Class}. Searches * all super types, enclosing types and interfaces. + * @deprecated as of Spring 4.0 in favor of {@link ResolvableType} */ + @Deprecated + @SuppressWarnings("rawtypes") public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) { Map<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz); - if (typeVariableMap == null) { typeVariableMap = new HashMap<TypeVariable, Type>(); + buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap); + typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap)); + } + return typeVariableMap; + } - // interfaces - extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap); - - try { - // super class - Class<?> type = clazz; - while (type.getSuperclass() != null && !Object.class.equals(type.getSuperclass())) { - Type genericType = type.getGenericSuperclass(); - if (genericType instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) genericType; - populateTypeMapFromParameterizedType(pt, typeVariableMap); + @SuppressWarnings("rawtypes") + private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable, Type> typeVariableMap) { + if (type != ResolvableType.NONE) { + if (type.getType() instanceof ParameterizedType) { + TypeVariable<?>[] variables = type.resolve().getTypeParameters(); + for (int i = 0; i < variables.length; i++) { + ResolvableType generic = type.getGeneric(i); + while (generic.getType() instanceof TypeVariable<?>) { + generic = generic.resolveType(); } - extractTypeVariablesFromGenericInterfaces(type.getSuperclass().getGenericInterfaces(), typeVariableMap); - type = type.getSuperclass(); - } - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - ignore and continue with member class check - } - - try { - // enclosing class - Class<?> type = clazz; - while (type.isMemberClass()) { - Type genericType = type.getGenericSuperclass(); - if (genericType instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) genericType; - populateTypeMapFromParameterizedType(pt, typeVariableMap); + if (generic != ResolvableType.NONE) { + typeVariableMap.put(variables[i], generic.getType()); } - type = type.getEnclosingClass(); } } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - ignore and preserve previously accumulated type variables + buildTypeVariableMap(type.getSuperType(), typeVariableMap); + for (ResolvableType interfaceType : type.getInterfaces()) { + buildTypeVariableMap(interfaceType, typeVariableMap); + } + if (type.resolve().isMemberClass()) { + buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap); } - - typeVariableCache.put(clazz, typeVariableMap); } - - return typeVariableMap; } - /** - * Extracts the bound {@code Type} for a given {@link TypeVariable}. - */ - static Type extractBoundForTypeVariable(TypeVariable typeVariable) { - Type[] bounds = typeVariable.getBounds(); - if (bounds.length == 0) { - return Object.class; - } - Type bound = bounds[0]; - if (bound instanceof TypeVariable) { - bound = extractBoundForTypeVariable((TypeVariable) bound); + + @SuppressWarnings({"serial", "rawtypes"}) + private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver { + + private final Map<TypeVariable, Type> typeVariableMap; + + public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) { + this.typeVariableMap = typeVariableMap; } - return bound; - } - private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map<TypeVariable, Type> typeVariableMap) { - for (Type genericInterface : genericInterfaces) { - if (genericInterface instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) genericInterface; - populateTypeMapFromParameterizedType(pt, typeVariableMap); - if (pt.getRawType() instanceof Class) { - extractTypeVariablesFromGenericInterfaces( - ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap); - } - } - else if (genericInterface instanceof Class) { - extractTypeVariablesFromGenericInterfaces( - ((Class) genericInterface).getGenericInterfaces(), typeVariableMap); - } + @Override + public ResolvableType resolveVariable(TypeVariable<?> variable) { + Type type = this.typeVariableMap.get(variable); + return (type != null ? ResolvableType.forType(type) : null); } - } - /** - * Read the {@link TypeVariable TypeVariables} from the supplied {@link ParameterizedType} - * and add mappings corresponding to the {@link TypeVariable#getName TypeVariable name} -> - * concrete type to the supplied {@link Map}. - * <p>Consider this case: - * <pre class="code> - * public interface Foo<S, T> { - * .. - * } - * - * public class FooImpl implements Foo<String, Integer> { - * .. - * }</pre> - * For '{@code FooImpl}' the following mappings would be added to the {@link Map}: - * {S=java.lang.String, T=java.lang.Integer}. - */ - private static void populateTypeMapFromParameterizedType(ParameterizedType type, Map<TypeVariable, Type> typeVariableMap) { - if (type.getRawType() instanceof Class) { - Type[] actualTypeArguments = type.getActualTypeArguments(); - TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters(); - for (int i = 0; i < actualTypeArguments.length; i++) { - Type actualTypeArgument = actualTypeArguments[i]; - TypeVariable variable = typeVariables[i]; - if (actualTypeArgument instanceof Class) { - typeVariableMap.put(variable, actualTypeArgument); - } - else if (actualTypeArgument instanceof GenericArrayType) { - typeVariableMap.put(variable, actualTypeArgument); - } - else if (actualTypeArgument instanceof ParameterizedType) { - typeVariableMap.put(variable, actualTypeArgument); - } - else if (actualTypeArgument instanceof TypeVariable) { - // We have a type that is parameterized at instantiation time - // the nearest match on the bridge method will be the bounded type. - TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument; - Type resolvedType = typeVariableMap.get(typeVariableArgument); - if (resolvedType == null) { - resolvedType = extractBoundForTypeVariable(typeVariableArgument); - } - typeVariableMap.put(variable, resolvedType); - } - } + @Override + public Object getSource() { + return this.typeVariableMap; } } diff --git a/spring-core/src/main/java/org/springframework/core/JdkVersion.java b/spring-core/src/main/java/org/springframework/core/JdkVersion.java index a77f75ff..78ebc8b9 100644 --- a/spring-core/src/main/java/org/springframework/core/JdkVersion.java +++ b/spring-core/src/main/java/org/springframework/core/JdkVersion.java @@ -17,15 +17,16 @@ package org.springframework.core; /** - * Internal helper class used to find the Java/JVM version - * that Spring is operating on, to allow for automatically - * adapting to the present platform's capabilities. + * Internal helper class used to find the Java/JVM version that Spring is + * operating on, to allow for automatically adapting to the present platform's + * capabilities. * - * <p>Note that Spring requires JVM 1.5 or higher, as of Spring 3.0. + * <p>Note that Spring requires JVM 1.6 or higher, as of Spring 4.0. * * @author Rod Johnson * @author Juergen Hoeller * @author Rick Evans + * @author Sam Brannen */ public abstract class JdkVersion { @@ -59,6 +60,11 @@ public abstract class JdkVersion { */ public static final int JAVA_18 = 5; + /** + * Constant identifying the 1.9 JVM (Java 9). + */ + public static final int JAVA_19 = 6; + private static final String javaVersion; @@ -67,18 +73,18 @@ public abstract class JdkVersion { static { javaVersion = System.getProperty("java.version"); // version String should look like "1.4.2_10" - if (javaVersion.contains("1.8.")) { + if (javaVersion.contains("1.9.")) { + majorJavaVersion = JAVA_19; + } + else if (javaVersion.contains("1.8.")) { majorJavaVersion = JAVA_18; } else if (javaVersion.contains("1.7.")) { majorJavaVersion = JAVA_17; } - else if (javaVersion.contains("1.6.")) { - majorJavaVersion = JAVA_16; - } else { - // else leave 1.5 as default (it's either 1.5 or unknown) - majorJavaVersion = JAVA_15; + // else leave 1.6 as default (it's either 1.6 or unknown) + majorJavaVersion = JAVA_16; } } @@ -96,61 +102,14 @@ public abstract class JdkVersion { /** * Get the major version code. This means we can do things like * {@code if (getMajorJavaVersion() >= JAVA_17)}. - * @return a code comparable to the JAVA_XX codes in this class - * @see #JAVA_13 - * @see #JAVA_14 - * @see #JAVA_15 + * @return a code comparable to the {@code JAVA_XX} codes in this class * @see #JAVA_16 - * @see #JAVA_17 + * @see #JAVA_17 + * @see #JAVA_18 + * @see #JAVA_19 */ public static int getMajorJavaVersion() { return majorJavaVersion; } - - /** - * Convenience method to determine if the current JVM is at least Java 1.4. - * @return {@code true} if the current JVM is at least Java 1.4 - * @deprecated as of Spring 3.0 which requires Java 1.5+ - * @see #getMajorJavaVersion() - * @see #JAVA_14 - * @see #JAVA_15 - * @see #JAVA_16 - * @see #JAVA_17 - */ - @Deprecated - public static boolean isAtLeastJava14() { - return true; - } - - /** - * Convenience method to determine if the current JVM is at least - * Java 1.5 (Java 5). - * @return {@code true} if the current JVM is at least Java 1.5 - * @deprecated as of Spring 3.0 which requires Java 1.5+ - * @see #getMajorJavaVersion() - * @see #JAVA_15 - * @see #JAVA_16 - * @see #JAVA_17 - */ - @Deprecated - public static boolean isAtLeastJava15() { - return true; - } - - /** - * Convenience method to determine if the current JVM is at least - * Java 1.6 (Java 6). - * @return {@code true} if the current JVM is at least Java 1.6 - * @deprecated as of Spring 3.0, in favor of reflective checks for - * the specific Java 1.6 classes of interest - * @see #getMajorJavaVersion() - * @see #JAVA_16 - * @see #JAVA_17 - */ - @Deprecated - public static boolean isAtLeastJava16() { - return (majorJavaVersion >= JAVA_16); - } - } diff --git a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java index 151dcb30..cc09fd80 100644 --- a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java @@ -64,6 +64,7 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD new ConcurrentHashMap<Class<?>, Map<Member, String[]>>(32); + @Override public String[] getParameterNames(Method method) { Method originalMethod = BridgeMethodResolver.findBridgedMethod(method); Class<?> declaringClass = originalMethod.getDeclaringClass(); @@ -78,6 +79,7 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD return null; } + @Override public String[] getParameterNames(Constructor<?> ctor) { Class<?> declaringClass = ctor.getDeclaringClass(); Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass); diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 48e72a1e..0174d137 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -23,7 +23,6 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.util.HashMap; import java.util.Map; @@ -48,6 +47,13 @@ public class MethodParameter { private final int parameterIndex; + private int nestingLevel = 1; + + /** Map from Integer level to Integer type index */ + Map<Integer, Integer> typeIndexesPerLevel; + + private volatile Class<?> containingClass; + private volatile Class<?> parameterType; private volatile Type genericParameterType; @@ -58,13 +64,6 @@ public class MethodParameter { private volatile String parameterName; - private int nestingLevel = 1; - - /** Map from Integer level to Integer type index */ - Map<Integer, Integer> typeIndexesPerLevel; - - Map<TypeVariable, Type> typeVariableMap; - /** * Create a new MethodParameter for the given method, with nesting level 1. @@ -128,14 +127,14 @@ public class MethodParameter { this.method = original.method; this.constructor = original.constructor; this.parameterIndex = original.parameterIndex; + this.nestingLevel = original.nestingLevel; + this.typeIndexesPerLevel = original.typeIndexesPerLevel; + this.containingClass = original.containingClass; this.parameterType = original.parameterType; this.genericParameterType = original.genericParameterType; this.parameterAnnotations = original.parameterAnnotations; this.parameterNameDiscoverer = original.parameterNameDiscoverer; this.parameterName = original.parameterName; - this.nestingLevel = original.nestingLevel; - this.typeIndexesPerLevel = original.typeIndexesPerLevel; - this.typeVariableMap = original.typeVariableMap; } @@ -161,16 +160,32 @@ public class MethodParameter { * Returns the wrapped member. * @return the Method or Constructor as Member */ - private Member getMember() { - return (this.method != null ? this.method : this.constructor); + public Member getMember() { + // NOTE: no ternary expression to retain JDK <8 compatibility even when using + // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable + // as common type, with that new base class not available on older JDKs) + if (this.method != null) { + return this.method; + } + else { + return this.constructor; + } } /** * Returns the wrapped annotated element. * @return the Method or Constructor as AnnotatedElement */ - private AnnotatedElement getAnnotatedElement() { - return (this.method != null ? this.method : this.constructor); + public AnnotatedElement getAnnotatedElement() { + // NOTE: no ternary expression to retain JDK <8 compatibility even when using + // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable + // as common type, with that new base class not available on older JDKs) + if (this.method != null) { + return this.method; + } + else { + return this.constructor; + } } /** @@ -189,6 +204,84 @@ public class MethodParameter { } /** + * Increase this parameter's nesting level. + * @see #getNestingLevel() + */ + public void increaseNestingLevel() { + this.nestingLevel++; + } + + /** + * Decrease this parameter's nesting level. + * @see #getNestingLevel() + */ + public void decreaseNestingLevel() { + getTypeIndexesPerLevel().remove(this.nestingLevel); + this.nestingLevel--; + } + + /** + * Return the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List). + */ + public int getNestingLevel() { + return this.nestingLevel; + } + + /** + * Set the type index for the current nesting level. + * @param typeIndex the corresponding type index + * (or {@code null} for the default type index) + * @see #getNestingLevel() + */ + public void setTypeIndexForCurrentLevel(int typeIndex) { + getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex); + } + + /** + * Return the type index for the current nesting level. + * @return the corresponding type index, or {@code null} + * if none specified (indicating the default type index) + * @see #getNestingLevel() + */ + public Integer getTypeIndexForCurrentLevel() { + return getTypeIndexForLevel(this.nestingLevel); + } + + /** + * Return the type index for the specified nesting level. + * @param nestingLevel the nesting level to check + * @return the corresponding type index, or {@code null} + * if none specified (indicating the default type index) + */ + public Integer getTypeIndexForLevel(int nestingLevel) { + return getTypeIndexesPerLevel().get(nestingLevel); + } + + /** + * Obtain the (lazily constructed) type-indexes-per-level Map. + */ + private Map<Integer, Integer> getTypeIndexesPerLevel() { + if (this.typeIndexesPerLevel == null) { + this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4); + } + return this.typeIndexesPerLevel; + } + + + /** + * Set a containing class to resolve the parameter type against. + */ + void setContainingClass(Class<?> containingClass) { + this.containingClass = containingClass; + } + + public Class<?> getContainingClass() { + return (this.containingClass != null ? this.containingClass : getDeclaringClass()); + } + + /** * Set a resolved (generic) parameter type. */ void setParameterType(Class<?> parameterType) { @@ -236,7 +329,8 @@ public class MethodParameter { Type type = getGenericParameterType(); if (type instanceof ParameterizedType) { Integer index = getTypeIndexForCurrentLevel(); - Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0]; + Type[] args = ((ParameterizedType) type).getActualTypeArguments(); + Type arg = args[index != null ? index : args.length - 1]; if (arg instanceof Class) { return (Class<?>) arg; } @@ -347,71 +441,6 @@ public class MethodParameter { return this.parameterName; } - /** - * Increase this parameter's nesting level. - * @see #getNestingLevel() - */ - public void increaseNestingLevel() { - this.nestingLevel++; - } - - /** - * Decrease this parameter's nesting level. - * @see #getNestingLevel() - */ - public void decreaseNestingLevel() { - getTypeIndexesPerLevel().remove(this.nestingLevel); - this.nestingLevel--; - } - - /** - * Return the nesting level of the target type - * (typically 1; e.g. in case of a List of Lists, 1 would indicate the - * nested List, whereas 2 would indicate the element of the nested List). - */ - public int getNestingLevel() { - return this.nestingLevel; - } - - /** - * Set the type index for the current nesting level. - * @param typeIndex the corresponding type index - * (or {@code null} for the default type index) - * @see #getNestingLevel() - */ - public void setTypeIndexForCurrentLevel(int typeIndex) { - getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex); - } - - /** - * Return the type index for the current nesting level. - * @return the corresponding type index, or {@code null} - * if none specified (indicating the default type index) - * @see #getNestingLevel() - */ - public Integer getTypeIndexForCurrentLevel() { - return getTypeIndexForLevel(this.nestingLevel); - } - - /** - * Return the type index for the specified nesting level. - * @param nestingLevel the nesting level to check - * @return the corresponding type index, or {@code null} - * if none specified (indicating the default type index) - */ - public Integer getTypeIndexForLevel(int nestingLevel) { - return getTypeIndexesPerLevel().get(nestingLevel); - } - - /** - * Obtain the (lazily constructed) type-indexes-per-level Map. - */ - private Map<Integer, Integer> getTypeIndexesPerLevel() { - if (this.typeIndexesPerLevel == null) { - this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4); - } - return this.typeIndexesPerLevel; - } @Override public boolean equals(Object other) { diff --git a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java index 8f72b5ea..23c7ceb1 100644 --- a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java +++ b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java @@ -109,7 +109,7 @@ public abstract class NestedCheckedException extends Exception { * @param exType the exception type to look for * @return whether there is a nested exception of the specified type */ - public boolean contains(Class exType) { + public boolean contains(Class<?> exType) { if (exType == null) { return false; } diff --git a/spring-core/src/main/java/org/springframework/core/NestedIOException.java b/spring-core/src/main/java/org/springframework/core/NestedIOException.java index 9bd7b88e..1b9927ea 100644 --- a/spring-core/src/main/java/org/springframework/core/NestedIOException.java +++ b/spring-core/src/main/java/org/springframework/core/NestedIOException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,8 +60,7 @@ public class NestedIOException extends IOException { * @param cause the nested exception */ public NestedIOException(String msg, Throwable cause) { - super(msg); - initCause(cause); + super(msg, cause); } diff --git a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java index 520c8bc9..8d20d81f 100644 --- a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java +++ b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java @@ -110,7 +110,7 @@ public abstract class NestedRuntimeException extends RuntimeException { * @param exType the exception type to look for * @return whether there is a nested exception of the specified type */ - public boolean contains(Class exType) { + public boolean contains(Class<?> exType) { if (exType == null) { return false; } diff --git a/spring-core/src/main/java/org/springframework/core/OrderComparator.java b/spring-core/src/main/java/org/springframework/core/OrderComparator.java index 16158ca1..0a261279 100644 --- a/spring-core/src/main/java/org/springframework/core/OrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/OrderComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ public class OrderComparator implements Comparator<Object> { public static final OrderComparator INSTANCE = new OrderComparator(); + @Override public int compare(Object o1, Object o2) { boolean p1 = (o1 instanceof PriorityOrdered); boolean p2 = (o2 instanceof PriorityOrdered); @@ -97,4 +98,21 @@ public class OrderComparator implements Comparator<Object> { } } + /** + * Sort the given array or List with a default OrderComparator, + * if necessary. Simply skips sorting when given any other value. + * <p>Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param value the array or List to sort + * @see java.util.Arrays#sort(Object[], java.util.Comparator) + */ + public static void sortIfNecessary(Object value) { + if (value instanceof Object[]) { + sort((Object[]) value); + } + else if (value instanceof List) { + sort((List<?>) value); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java index 597a3923..9fbf0eed 100644 --- a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java +++ b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java @@ -55,8 +55,8 @@ public class OverridingClassLoader extends DecoratingClassLoader { @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - Class result = null; + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class<?> result = null; if (isEligibleForOverriding(name)) { result = loadClassForOverriding(name); } @@ -90,8 +90,8 @@ public class OverridingClassLoader extends DecoratingClassLoader { * @return the Class object, or {@code null} if no class defined for that name * @throws ClassNotFoundException if the class for the given name couldn't be loaded */ - protected Class loadClassForOverriding(String name) throws ClassNotFoundException { - Class result = findLoadedClass(name); + protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException { + Class<?> result = findLoadedClass(name); if (result == null) { byte[] bytes = loadBytesForClass(name); if (bytes != null) { diff --git a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java index d7165b3e..e64f5890 100644 --- a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java +++ b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java @@ -61,7 +61,7 @@ public abstract class ParameterizedTypeReference<T> { @Override public boolean equals(Object obj) { return (this == obj || (obj instanceof ParameterizedTypeReference && - this.type.equals(((ParameterizedTypeReference) obj).type))); + this.type.equals(((ParameterizedTypeReference<?>) obj).type))); } @Override diff --git a/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java index e65e88cd..086a0cc9 100644 --- a/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java @@ -48,6 +48,7 @@ public class PrioritizedParameterNameDiscoverer implements ParameterNameDiscover } + @Override public String[] getParameterNames(Method method) { for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) { String[] result = pnd.getParameterNames(method); @@ -58,7 +59,8 @@ public class PrioritizedParameterNameDiscoverer implements ParameterNameDiscover return null; } - public String[] getParameterNames(Constructor ctor) { + @Override + public String[] getParameterNames(Constructor<?> ctor) { for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) { String[] result = pnd.getParameterNames(ctor); if (result != null) { diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java new file mode 100644 index 00000000..78c7e88f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -0,0 +1,1365 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; + +import org.springframework.core.SerializableTypeWrapper.FieldTypeProvider; +import org.springframework.core.SerializableTypeWrapper.MethodParameterTypeProvider; +import org.springframework.core.SerializableTypeWrapper.TypeProvider; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Encapsulates a Java {@link java.lang.reflect.Type}, providing access to + * {@link #getSuperType() supertypes}, {@link #getInterfaces() interfaces}, and + * {@link #getGeneric(int...) generic parameters} along with the ability to ultimately + * {@link #resolve() resolve} to a {@link java.lang.Class}. + * + * <p>{@code ResolvableTypes} may be obtained from {@link #forField(Field) fields}, + * {@link #forMethodParameter(Method, int) method parameters}, + * {@link #forMethodReturnType(Method) method returns} or + * {@link #forClass(Class) classes}. Most methods on this class will themselves return + * {@link ResolvableType}s, allowing easy navigation. For example: + * <pre class="code"> + * private HashMap<Integer, List<String>> myMap; + * + * public void example() { + * ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap")); + * t.getSuperType(); // AbstractMap<Integer, List<String>> + * t.asMap(); // Map<Integer, List<String>> + * t.getGeneric(0).resolve(); // Integer + * t.getGeneric(1).resolve(); // List + * t.getGeneric(1); // List<String> + * t.resolveGeneric(1, 0); // String + * } + * </pre> + * + * @author Phillip Webb + * @author Juergen Hoeller + * @since 4.0 + * @see #forField(Field) + * @see #forMethodParameter(Method, int) + * @see #forMethodReturnType(Method) + * @see #forConstructorParameter(Constructor, int) + * @see #forClass(Class) + * @see #forType(Type) + */ +@SuppressWarnings("serial") +public final class ResolvableType implements Serializable { + + /** + * {@code ResolvableType} returned when no value is available. {@code NONE} is used + * in preference to {@code null} so that multiple method calls can be safely chained. + */ + public static final ResolvableType NONE = new ResolvableType(null, null, null, null); + + private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0]; + + private static final ConcurrentReferenceHashMap<ResolvableType, ResolvableType> cache = + new ConcurrentReferenceHashMap<ResolvableType, ResolvableType>(256); + + + /** + * The underlying Java type being managed (only ever {@code null} for {@link #NONE}). + */ + private final Type type; + + /** + * Optional provider for the type. + */ + private final TypeProvider typeProvider; + + /** + * The {@code VariableResolver} to use or {@code null} if no resolver is available. + */ + private final VariableResolver variableResolver; + + /** + * The component type for an array or {@code null} if the type should be deduced. + */ + private final ResolvableType componentType; + + /** + * Copy of the resolved value. + */ + private final Class<?> resolved; + + private ResolvableType superType; + + private ResolvableType[] interfaces; + + private ResolvableType[] generics; + + + /** + * Private constructor used to create a new {@link ResolvableType} for resolution purposes. + */ + private ResolvableType( + Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) { + + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = componentType; + this.resolved = resolveClass(); + } + + /** + * Private constructor used to create a new {@link ResolvableType} for cache key purposes. + */ + private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = null; + this.resolved = null; + } + + + /** + * Return the underling Java {@link Type} being managed. With the exception of + * the {@link #NONE} constant, this method will never return {@code null}. + */ + public Type getType() { + return SerializableTypeWrapper.unwrap(this.type); + } + + /** + * Return the underlying Java {@link Class} being managed, if available; + * otherwise {@code null}. + */ + public Class<?> getRawClass() { + Type rawType = this.type; + if (rawType instanceof ParameterizedType) { + rawType = ((ParameterizedType) rawType).getRawType(); + } + return (rawType instanceof Class ? (Class<?>) rawType : null); + } + + /** + * Return the underlying source of the resolvable type. Will return a {@link Field}, + * {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType} + * was constructed. With the exception of the {@link #NONE} constant, this method will + * never return {@code null}. This method is primarily to provide access to additional + * type information or meta-data that alternative JVM languages may provide. + */ + public Object getSource() { + Object source = (this.typeProvider != null ? this.typeProvider.getSource() : null); + return (source != null ? source : this.type); + } + + /** + * Determine whether this {@code ResolvableType} is assignable from the + * specified other type. + * <p>Attempts to follow the same rules as the Java compiler, considering + * whether both the {@link #resolve() resolved} {@code Class} is + * {@link Class#isAssignableFrom(Class) assignable from} the given type + * as well as whether all {@link #getGenerics() generics} are assignable. + * @param other the type to be checked against + * @return {@code true} if the specified other type can be assigned to this + * {@code ResolvableType}; {@code false} otherwise + */ + public boolean isAssignableFrom(ResolvableType other) { + return isAssignableFrom(other, null); + } + + private boolean isAssignableFrom(ResolvableType other, Map<Type, Type> matchedBefore) { + Assert.notNull(other, "ResolvableType must not be null"); + + // If we cannot resolve types, we are not assignable + if (this == NONE || other == NONE) { + return false; + } + + // Deal with array by delegating to the component type + if (isArray()) { + return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType())); + } + + if (matchedBefore != null && matchedBefore.get(this.type) == other.type) { + return true; + } + + // Deal with wildcard bounds + WildcardBounds ourBounds = WildcardBounds.get(this); + WildcardBounds typeBounds = WildcardBounds.get(other); + + // In the from X is assignable to <? extends Number> + if (typeBounds != null) { + return (ourBounds != null && ourBounds.isSameKind(typeBounds) && + ourBounds.isAssignableFrom(typeBounds.getBounds())); + } + + // In the form <? extends Number> is assignable to X... + if (ourBounds != null) { + return ourBounds.isAssignableFrom(other); + } + + // Main assignability check about to follow + boolean exactMatch = (matchedBefore != null); // We're checking nested generic variables now... + boolean checkGenerics = true; + Class<?> ourResolved = null; + if (this.type instanceof TypeVariable) { + TypeVariable<?> variable = (TypeVariable<?>) this.type; + // Try default variable resolution + if (this.variableResolver != null) { + ResolvableType resolved = this.variableResolver.resolveVariable(variable); + if (resolved != null) { + ourResolved = resolved.resolve(); + } + } + if (ourResolved == null) { + // Try variable resolution against target type + if (other.variableResolver != null) { + ResolvableType resolved = other.variableResolver.resolveVariable(variable); + if (resolved != null) { + ourResolved = resolved.resolve(); + checkGenerics = false; + } + } + } + if (ourResolved == null) { + // Unresolved type variable, potentially nested -> never insist on exact match + exactMatch = false; + } + } + if (ourResolved == null) { + ourResolved = resolve(Object.class); + } + Class<?> otherResolved = other.resolve(Object.class); + + // We need an exact type match for generics + // List<CharSequence> is not assignable from List<String> + if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) { + return false; + } + + if (checkGenerics) { + // Recursively check each generic + ResolvableType[] ourGenerics = getGenerics(); + ResolvableType[] typeGenerics = other.as(ourResolved).getGenerics(); + if (ourGenerics.length != typeGenerics.length) { + return false; + } + if (matchedBefore == null) { + matchedBefore = new IdentityHashMap<Type, Type>(1); + } + matchedBefore.put(this.type, other.type); + for (int i = 0; i < ourGenerics.length; i++) { + if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) { + return false; + } + } + } + + return true; + } + + /** + * Return {@code true} if this type resolves to a Class that represents an array. + * @see #getComponentType() + */ + public boolean isArray() { + if (this == NONE) { + return false; + } + return (((this.type instanceof Class && ((Class<?>) this.type).isArray())) || + this.type instanceof GenericArrayType || resolveType().isArray()); + } + + /** + * Return the ResolvableType representing the component type of the array or + * {@link #NONE} if this type does not represent an array. + * @see #isArray() + */ + public ResolvableType getComponentType() { + if (this == NONE) { + return NONE; + } + if (this.componentType != null) { + return this.componentType; + } + if (this.type instanceof Class) { + Class<?> componentType = ((Class<?>) this.type).getComponentType(); + return forType(componentType, this.variableResolver); + } + if (this.type instanceof GenericArrayType) { + return forType(((GenericArrayType) this.type).getGenericComponentType(), this.variableResolver); + } + return resolveType().getComponentType(); + } + + /** + * Convenience method to return this type as a resolvable {@link Collection} type. + * Returns {@link #NONE} if this type does not implement or extend + * {@link Collection}. + * @see #as(Class) + * @see #asMap() + */ + public ResolvableType asCollection() { + return as(Collection.class); + } + + /** + * Convenience method to return this type as a resolvable {@link Map} type. + * Returns {@link #NONE} if this type does not implement or extend + * {@link Map}. + * @see #as(Class) + * @see #asCollection() + */ + public ResolvableType asMap() { + return as(Map.class); + } + + /** + * Return this type as a {@link ResolvableType} of the specified class. Searches + * {@link #getSuperType() supertype} and {@link #getInterfaces() interface} + * hierarchies to find a match, returning {@link #NONE} if this type does not + * implement or extend the specified class. + * @param type the required class type + * @return a {@link ResolvableType} representing this object as the specified + * type or {@link #NONE} + * @see #asCollection() + * @see #asMap() + * @see #getSuperType() + * @see #getInterfaces() + */ + public ResolvableType as(Class<?> type) { + if (this == NONE) { + return NONE; + } + if (ObjectUtils.nullSafeEquals(resolve(), type)) { + return this; + } + for (ResolvableType interfaceType : getInterfaces()) { + ResolvableType interfaceAsType = interfaceType.as(type); + if (interfaceAsType != NONE) { + return interfaceAsType; + } + } + return getSuperType().as(type); + } + + /** + * Return a {@link ResolvableType} representing the direct supertype of this type. + * If no supertype is available this method returns {@link #NONE}. + * @see #getInterfaces() + */ + public ResolvableType getSuperType() { + Class<?> resolved = resolve(); + if (resolved == null || resolved.getGenericSuperclass() == null) { + return NONE; + } + if (this.superType == null) { + this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), + asVariableResolver()); + } + return this.superType; + } + + /** + * Return a {@link ResolvableType} array representing the direct interfaces + * implemented by this type. If this type does not implement any interfaces an + * empty array is returned. + * @see #getSuperType() + */ + public ResolvableType[] getInterfaces() { + Class<?> resolved = resolve(); + if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) { + return EMPTY_TYPES_ARRAY; + } + if (this.interfaces == null) { + this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), + asVariableResolver()); + } + return this.interfaces; + } + + /** + * Return {@code true} if this type contains generic parameters. + * @see #getGeneric(int...) + * @see #getGenerics() + */ + public boolean hasGenerics() { + return (getGenerics().length > 0); + } + + /** + * Return {@code true} if this type contains unresolvable generics only, + * that is, no substitute for any of its declared type variables. + */ + boolean isEntirelyUnresolvable() { + if (this == NONE) { + return false; + } + ResolvableType[] generics = getGenerics(); + for (ResolvableType generic : generics) { + if (!generic.isUnresolvableTypeVariable() && !generic.isWildcardWithoutBounds()) { + return false; + } + } + return true; + } + + /** + * Determine whether the underlying type has any unresolvable generics: + * either through an unresolvable type variable on the type itself + * or through implementing a generic interface in a raw fashion, + * i.e. without substituting that interface's type variables. + * The result will be {@code true} only in those two scenarios. + */ + public boolean hasUnresolvableGenerics() { + if (this == NONE) { + return false; + } + ResolvableType[] generics = getGenerics(); + for (ResolvableType generic : generics) { + if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds()) { + return true; + } + } + Class<?> resolved = resolve(); + if (resolved != null) { + for (Type genericInterface : resolved.getGenericInterfaces()) { + if (genericInterface instanceof Class) { + if (forClass((Class<?>) genericInterface).hasGenerics()) { + return true; + } + } + } + return getSuperType().hasUnresolvableGenerics(); + } + return false; + } + + /** + * Determine whether the underlying type is a type variable that + * cannot be resolved through the associated variable resolver. + */ + private boolean isUnresolvableTypeVariable() { + if (this.type instanceof TypeVariable) { + if (this.variableResolver == null) { + return true; + } + TypeVariable<?> variable = (TypeVariable<?>) this.type; + ResolvableType resolved = this.variableResolver.resolveVariable(variable); + if (resolved == null || resolved.isUnresolvableTypeVariable()) { + return true; + } + } + return false; + } + + /** + * Determine whether the underlying type represents a wildcard + * without specific bounds (i.e., equal to {@code ? extends Object}). + */ + private boolean isWildcardWithoutBounds() { + if (this.type instanceof WildcardType) { + WildcardType wt = (WildcardType) this.type; + if (wt.getLowerBounds().length == 0) { + Type[] upperBounds = wt.getUpperBounds(); + if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class.equals(upperBounds[0]))) { + return true; + } + } + } + return false; + } + + /** + * Return a {@link ResolvableType} for the specified nesting level. See + * {@link #getNested(int, Map)} for details. + * @param nestingLevel the nesting level + * @return the {@link ResolvableType} type, or {@code #NONE} + */ + public ResolvableType getNested(int nestingLevel) { + return getNested(nestingLevel, null); + } + + /** + * Return a {@link ResolvableType} for the specified nesting level. The nesting level + * refers to the specific generic parameter that should be returned. A nesting level + * of 1 indicates this type; 2 indicates the first nested generic; 3 the second; and so + * on. For example, given {@code List<Set<Integer>>} level 1 refers to the + * {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}. + * <p>The {@code typeIndexesPerLevel} map can be used to reference a specific generic + * for the given level. For example, an index of 0 would refer to a {@code Map} key; + * whereas, 1 would refer to the value. If the map does not contain a value for a + * specific level the last generic will be used (e.g. a {@code Map} value). + * <p>Nesting levels may also apply to array types; for example given + * {@code String[]}, a nesting level of 2 refers to {@code String}. + * <p>If a type does not {@link #hasGenerics() contain} generics the + * {@link #getSuperType() supertype} hierarchy will be considered. + * @param nestingLevel the required nesting level, indexed from 1 for the current + * type, 2 for the first nested generic, 3 for the second and so on + * @param typeIndexesPerLevel a map containing the generic index for a given nesting + * level (may be {@code null}) + * @return a {@link ResolvableType} for the nested level or {@link #NONE} + */ + public ResolvableType getNested(int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) { + ResolvableType result = this; + for (int i = 2; i <= nestingLevel; i++) { + if (result.isArray()) { + result = result.getComponentType(); + } + else { + // Handle derived types + while (result != ResolvableType.NONE && !result.hasGenerics()) { + result = result.getSuperType(); + } + Integer index = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(i) : null); + index = (index == null ? result.getGenerics().length - 1 : index); + result = result.getGeneric(index); + } + } + return result; + } + + /** + * Return a {@link ResolvableType} representing the generic parameter for the given + * indexes. Indexes are zero based; for example given the type + * {@code Map<Integer, List<String>>}, {@code getGeneric(0)} will access the + * {@code Integer}. Nested generics can be accessed by specifying multiple indexes; + * for example {@code getGeneric(1, 0)} will access the {@code String} from the nested + * {@code List}. For convenience, if no indexes are specified the first generic is + * returned. + * <p>If no generic is available at the specified indexes {@link #NONE} is returned. + * @param indexes the indexes that refer to the generic parameter (may be omitted to + * return the first generic) + * @return a {@link ResolvableType} for the specified generic or {@link #NONE} + * @see #hasGenerics() + * @see #getGenerics() + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public ResolvableType getGeneric(int... indexes) { + try { + if (indexes == null || indexes.length == 0) { + return getGenerics()[0]; + } + ResolvableType generic = this; + for (int index : indexes) { + generic = generic.getGenerics()[index]; + } + return generic; + } + catch (IndexOutOfBoundsException ex) { + return NONE; + } + } + + /** + * Return an array of {@link ResolvableType}s representing the generic parameters of + * this type. If no generics are available an empty array is returned. If you need to + * access a specific generic consider using the {@link #getGeneric(int...)} method as + * it allows access to nested generics and protects against + * {@code IndexOutOfBoundsExceptions}. + * @return an array of {@link ResolvableType}s representing the generic parameters + * (never {@code null}) + * @see #hasGenerics() + * @see #getGeneric(int...) + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public ResolvableType[] getGenerics() { + if (this == NONE) { + return EMPTY_TYPES_ARRAY; + } + if (this.generics == null) { + if (this.type instanceof Class<?>) { + Class<?> typeClass = (Class<?>) this.type; + this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver); + } + else if (this.type instanceof ParameterizedType) { + Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments(); + ResolvableType[] generics = new ResolvableType[actualTypeArguments.length]; + for (int i = 0; i < actualTypeArguments.length; i++) { + generics[i] = forType(actualTypeArguments[i], this.variableResolver); + } + this.generics = generics; + } + else { + this.generics = resolveType().getGenerics(); + } + } + return this.generics; + } + + /** + * Convenience method that will {@link #getGenerics() get} and + * {@link #resolve() resolve} generic parameters. + * @return an array of resolved generic parameters (the resulting array + * will never be {@code null}, but it may contain {@code null} elements}) + * @see #getGenerics() + * @see #resolve() + */ + public Class<?>[] resolveGenerics() { + return resolveGenerics(null); + } + + /** + * Convenience method that will {@link #getGenerics() get} and {@link #resolve() + * resolve} generic parameters, using the specified {@code fallback} if any type + * cannot be resolved. + * @param fallback the fallback class to use if resolution fails (may be {@code null}) + * @return an array of resolved generic parameters (the resulting array will never be + * {@code null}, but it may contain {@code null} elements}) + * @see #getGenerics() + * @see #resolve() + */ + public Class<?>[] resolveGenerics(Class<?> fallback) { + ResolvableType[] generics = getGenerics(); + Class<?>[] resolvedGenerics = new Class<?>[generics.length]; + for (int i = 0; i < generics.length; i++) { + resolvedGenerics[i] = generics[i].resolve(fallback); + } + return resolvedGenerics; + } + + /** + * Convenience method that will {@link #getGeneric(int...) get} and + * {@link #resolve() resolve} a specific generic parameters. + * @param indexes the indexes that refer to the generic parameter + * (may be omitted to return the first generic) + * @return a resolved {@link Class} or {@code null} + * @see #getGeneric(int...) + * @see #resolve() + */ + public Class<?> resolveGeneric(int... indexes) { + return getGeneric(indexes).resolve(); + } + + /** + * Resolve this type to a {@link java.lang.Class}, returning {@code null} + * if the type cannot be resolved. This method will consider bounds of + * {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails; + * however, bounds of {@code Object.class} will be ignored. + * @return the resolved {@link Class}, or {@code null} if not resolvable + * @see #resolve(Class) + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public Class<?> resolve() { + return resolve(null); + } + + /** + * Resolve this type to a {@link java.lang.Class}, returning the specified + * {@code fallback} if the type cannot be resolved. This method will consider bounds + * of {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails; + * however, bounds of {@code Object.class} will be ignored. + * @param fallback the fallback class to use if resolution fails (may be {@code null}) + * @return the resolved {@link Class} or the {@code fallback} + * @see #resolve() + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public Class<?> resolve(Class<?> fallback) { + return (this.resolved != null ? this.resolved : fallback); + } + + private Class<?> resolveClass() { + if (this.type instanceof Class<?> || this.type == null) { + return (Class<?>) this.type; + } + if (this.type instanceof GenericArrayType) { + Class<?> resolvedComponent = getComponentType().resolve(); + return (resolvedComponent != null ? Array.newInstance(resolvedComponent, 0).getClass() : null); + } + return resolveType().resolve(); + } + + /** + * Resolve this type by a single level, returning the resolved value or {@link #NONE}. + * <p>Note: The returned {@link ResolvableType} should only be used as an intermediary + * as it cannot be serialized. + */ + ResolvableType resolveType() { + if (this.type instanceof ParameterizedType) { + return forType(((ParameterizedType) this.type).getRawType(), this.variableResolver); + } + if (this.type instanceof WildcardType) { + Type resolved = resolveBounds(((WildcardType) this.type).getUpperBounds()); + if (resolved == null) { + resolved = resolveBounds(((WildcardType) this.type).getLowerBounds()); + } + return forType(resolved, this.variableResolver); + } + if (this.type instanceof TypeVariable) { + TypeVariable<?> variable = (TypeVariable<?>) this.type; + // Try default variable resolution + if (this.variableResolver != null) { + ResolvableType resolved = this.variableResolver.resolveVariable(variable); + if (resolved != null) { + return resolved; + } + } + // Fallback to bounds + return forType(resolveBounds(variable.getBounds()), this.variableResolver); + } + return NONE; + } + + private Type resolveBounds(Type[] bounds) { + if (ObjectUtils.isEmpty(bounds) || Object.class.equals(bounds[0])) { + return null; + } + return bounds[0]; + } + + private ResolvableType resolveVariable(TypeVariable<?> variable) { + if (this.type instanceof TypeVariable) { + return resolveType().resolveVariable(variable); + } + if (this.type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) this.type; + TypeVariable<?>[] variables = resolve().getTypeParameters(); + for (int i = 0; i < variables.length; i++) { + if (ObjectUtils.nullSafeEquals(variables[i].getName(), variable.getName())) { + Type actualType = parameterizedType.getActualTypeArguments()[i]; + return forType(actualType, this.variableResolver); + } + } + if (parameterizedType.getOwnerType() != null) { + return forType(parameterizedType.getOwnerType(), this.variableResolver).resolveVariable(variable); + } + } + if (this.variableResolver != null) { + return this.variableResolver.resolveVariable(variable); + } + return null; + } + + /** + * Return a String representation of this type in its fully resolved form + * (including any generic parameters). + */ + @Override + public String toString() { + if (isArray()) { + return getComponentType() + "[]"; + } + if (this.resolved == null) { + return "?"; + } + if (this.type instanceof TypeVariable) { + TypeVariable<?> variable = (TypeVariable<?>) this.type; + if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) { + // Don't bother with variable boundaries for toString()... + // Can cause infinite recursions in case of self-references + return "?"; + } + } + StringBuilder result = new StringBuilder(this.resolved.getName()); + if (hasGenerics()) { + result.append('<'); + result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", ")); + result.append('>'); + } + return result.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ResolvableType)) { + return false; + } + ResolvableType other = (ResolvableType) obj; + return (ObjectUtils.nullSafeEquals(this.type, other.type) && + ObjectUtils.nullSafeEquals(getSource(), other.getSource()) && + variableResolverSourceEquals(other.variableResolver) && + ObjectUtils.nullSafeEquals(this.componentType, other.componentType)); + } + + @Override + public int hashCode() { + int hashCode = ObjectUtils.nullSafeHashCode(this.type); + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(getSource()); + hashCode = 31 * hashCode + variableResolverSourceHashCode(); + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.componentType); + return hashCode; + } + + /** + * Custom serialization support for {@link #NONE}. + */ + private Object readResolve() throws ObjectStreamException { + return (this.type == null ? NONE : this); + } + + /** + * Adapts this {@link ResolvableType} to a {@link VariableResolver}. + */ + VariableResolver asVariableResolver() { + if (this == NONE) { + return null; + } + return new DefaultVariableResolver(); + } + + private boolean variableResolverSourceEquals(VariableResolver other) { + if (this.variableResolver == null) { + return (other == null); + } + if (other == null) { + return false; + } + return ObjectUtils.nullSafeEquals(this.variableResolver.getSource(), other.getSource()); + } + + private int variableResolverSourceHashCode() { + int hashCode = 0; + if (this.variableResolver != null) { + hashCode = ObjectUtils.nullSafeHashCode(this.variableResolver.getSource()); + } + return hashCode; + } + + private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) { + ResolvableType[] result = new ResolvableType[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = forType(types[i], owner); + } + return result; + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class}. For example + * {@code ResolvableType.forClass(MyArrayList.class)}. + * @param sourceClass the source class (must not be {@code null} + * @return a {@link ResolvableType} for the specified class + * @see #forClass(Class, Class) + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClass(Class<?> sourceClass) { + Assert.notNull(sourceClass, "Source class must not be null"); + return forType(sourceClass); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} with a given + * implementation. For example + * {@code ResolvableType.forClass(List.class, MyArrayList.class)}. + * @param sourceClass the source class (must not be {@code null} + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified class backed by the given + * implementation class + * @see #forClass(Class) + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClass(Class<?> sourceClass, Class<?> implementationClass) { + Assert.notNull(sourceClass, "Source class must not be null"); + ResolvableType asType = forType(implementationClass).as(sourceClass); + return (asType == NONE ? forType(sourceClass) : asType); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field}. + * @param field the source field + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field, Class) + */ + public static ResolvableType forField(Field field) { + Assert.notNull(field, "Field must not be null"); + return forType(null, new FieldTypeProvider(field), null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation. + * <p>Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation class. + * @param field the source field + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, Class<?> implementationClass) { + Assert.notNull(field, "Field must not be null"); + ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation. + * <p>Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation type. + * @param field the source field + * @param implementationType the implementation type + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, ResolvableType implementationType) { + Assert.notNull(field, "Field must not be null"); + implementationType = (implementationType == null ? NONE : implementationType); + ResolvableType owner = implementationType.as(field.getDeclaringClass()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with the + * given nesting level. + * @param field the source field + * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested + * generic type; etc) + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, int nestingLevel) { + Assert.notNull(field, "Field must not be null"); + return forType(null, new FieldTypeProvider(field), null).getNested(nestingLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation and the given nesting level. + * <p>Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation class. + * @param field the source field + * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested + * generic type; etc) + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, int nestingLevel, Class<?> implementationClass) { + Assert.notNull(field, "Field must not be null"); + ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()).getNested(nestingLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Constructor} parameter. + * @param constructor the source constructor (must not be {@code null}) + * @param parameterIndex the parameter index + * @return a {@link ResolvableType} for the specified constructor parameter + * @see #forConstructorParameter(Constructor, int, Class) + */ + public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) { + Assert.notNull(constructor, "Constructor must not be null"); + return forMethodParameter(new MethodParameter(constructor, parameterIndex)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Constructor} parameter + * with a given implementation. Use this variant when the class that declares the + * constructor includes generic parameter variables that are satisfied by the + * implementation class. + * @param constructor the source constructor (must not be {@code null}) + * @param parameterIndex the parameter index + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified constructor parameter + * @see #forConstructorParameter(Constructor, int) + */ + public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex, + Class<?> implementationClass) { + + Assert.notNull(constructor, "Constructor must not be null"); + MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} return type. + * @param method the source for the method return type + * @return a {@link ResolvableType} for the specified method return + * @see #forMethodReturnType(Method, Class) + */ + public static ResolvableType forMethodReturnType(Method method) { + Assert.notNull(method, "Method must not be null"); + return forMethodParameter(MethodParameter.forMethodOrConstructor(method, -1)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} return type. + * Use this variant when the class that declares the method includes generic + * parameter variables that are satisfied by the implementation class. + * @param method the source for the method return type + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified method return + * @see #forMethodReturnType(Method) + */ + public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) { + Assert.notNull(method, "Method must not be null"); + MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, -1); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} parameter. + * @param method the source method (must not be {@code null}) + * @param parameterIndex the parameter index + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int, Class) + * @see #forMethodParameter(MethodParameter) + */ + public static ResolvableType forMethodParameter(Method method, int parameterIndex) { + Assert.notNull(method, "Method must not be null"); + return forMethodParameter(new MethodParameter(method, parameterIndex)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} parameter with a + * given implementation. Use this variant when the class that declares the method + * includes generic parameter variables that are satisfied by the implementation class. + * @param method the source method (must not be {@code null}) + * @param parameterIndex the parameter index + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int, Class) + * @see #forMethodParameter(MethodParameter) + */ + public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class<?> implementationClass) { + Assert.notNull(method, "Method must not be null"); + MethodParameter methodParameter = new MethodParameter(method, parameterIndex); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); + } + + /** + * Return a {@link ResolvableType} for the specified {@link MethodParameter}. + * @param methodParameter the source method parameter (must not be {@code null}) + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int) + */ + public static ResolvableType forMethodParameter(MethodParameter methodParameter) { + return forMethodParameter(methodParameter, (Type) null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link MethodParameter} with a + * given implementation type. Use this variant when the class that declares the method + * includes generic parameter variables that are satisfied by the implementation type. + * @param methodParameter the source method parameter (must not be {@code null}) + * @param implementationType the implementation type + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(MethodParameter) + */ + public static ResolvableType forMethodParameter(MethodParameter methodParameter, ResolvableType implementationType) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + implementationType = (implementationType == null ? forType(methodParameter.getContainingClass()) : implementationType); + ResolvableType owner = implementationType.as(methodParameter.getDeclaringClass()); + return forType(null, new MethodParameterTypeProvider(methodParameter), + owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(), + methodParameter.typeIndexesPerLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link MethodParameter}, + * overriding the target type to resolve with a specific given type. + * @param methodParameter the source method parameter (must not be {@code null}) + * @param targetType the type to resolve (a part of the method parameter's type) + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int) + */ + public static ResolvableType forMethodParameter(MethodParameter methodParameter, Type targetType) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()); + return forType(targetType, new MethodParameterTypeProvider(methodParameter), + owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(), + methodParameter.typeIndexesPerLevel); + } + + /** + * Return a {@link ResolvableType} as a array of the specified {@code componentType}. + * @param componentType the component type + * @return a {@link ResolvableType} as an array of the specified component type + */ + public static ResolvableType forArrayComponent(ResolvableType componentType) { + Assert.notNull(componentType, "ComponentType must not be null"); + Class<?> arrayClass = Array.newInstance(componentType.resolve(), 0).getClass(); + return new ResolvableType(arrayClass, null, null, componentType); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. + * @param sourceClass the source class + * @param generics the generics of the class + * @return a {@link ResolvableType} for the specific class and generics + * @see #forClassWithGenerics(Class, ResolvableType...) + */ + public static ResolvableType forClassWithGenerics(Class<?> sourceClass, Class<?>... generics) { + Assert.notNull(sourceClass, "Source class must not be null"); + Assert.notNull(generics, "Generics must not be null"); + ResolvableType[] resolvableGenerics = new ResolvableType[generics.length]; + for (int i = 0; i < generics.length; i++) { + resolvableGenerics[i] = forClass(generics[i]); + } + return forClassWithGenerics(sourceClass, resolvableGenerics); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. + * @param sourceClass the source class + * @param generics the generics of the class + * @return a {@link ResolvableType} for the specific class and generics + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClassWithGenerics(Class<?> sourceClass, ResolvableType... generics) { + Assert.notNull(sourceClass, "Source class must not be null"); + Assert.notNull(generics, "Generics must not be null"); + TypeVariable<?>[] typeVariables = sourceClass.getTypeParameters(); + return forType(sourceClass, new TypeVariablesVariableResolver(typeVariables, generics)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type}. + * Note: The resulting {@link ResolvableType} may not be {@link Serializable}. + * @param type the source type or {@code null} + * @return a {@link ResolvableType} for the specified {@link Type} + * @see #forType(Type, ResolvableType) + */ + public static ResolvableType forType(Type type) { + return forType(type, null, null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by the given + * owner type. Note: The resulting {@link ResolvableType} may not be {@link Serializable}. + * @param type the source type or {@code null} + * @param owner the owner type used to resolve variables + * @return a {@link ResolvableType} for the specified {@link Type} and owner + * @see #forType(Type) + */ + public static ResolvableType forType(Type type, ResolvableType owner) { + VariableResolver variableResolver = null; + if (owner != null) { + variableResolver = owner.asVariableResolver(); + } + return forType(type, variableResolver); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by a given + * {@link VariableResolver}. + * @param type the source type or {@code null} + * @param variableResolver the variable resolver or {@code null} + * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} + */ + static ResolvableType forType(Type type, VariableResolver variableResolver) { + return forType(type, null, variableResolver); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by a given + * {@link VariableResolver}. + * @param type the source type or {@code null} + * @param typeProvider the type provider or {@code null} + * @param variableResolver the variable resolver or {@code null} + * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} + */ + static ResolvableType forType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { + if (type == null && typeProvider != null) { + type = SerializableTypeWrapper.forTypeProvider(typeProvider); + } + if (type == null) { + return NONE; + } + + // Purge empty entries on access since we don't have a clean-up thread or the like. + cache.purgeUnreferencedEntries(); + + // For simple Class references, build the wrapper right away - + // no expensive resolution necessary, so not worth caching... + if (type instanceof Class<?>) { + return new ResolvableType(type, typeProvider, variableResolver, null); + } + + // Check the cache - we may have a ResolvableType which has been resolved before... + ResolvableType key = new ResolvableType(type, typeProvider, variableResolver); + ResolvableType resolvableType = cache.get(key); + if (resolvableType == null) { + resolvableType = new ResolvableType(type, typeProvider, variableResolver, null); + cache.put(resolvableType, resolvableType); + } + return resolvableType; + } + + + /** + * Strategy interface used to resolve {@link TypeVariable}s. + */ + static interface VariableResolver extends Serializable { + + /** + * Return the source of the resolver (used for hashCode and equals). + */ + Object getSource(); + + /** + * Resolve the specified variable. + * @param variable the variable to resolve + * @return the resolved variable or {@code null} + */ + ResolvableType resolveVariable(TypeVariable<?> variable); + } + + + @SuppressWarnings("serial") + private class DefaultVariableResolver implements VariableResolver { + + @Override + public ResolvableType resolveVariable(TypeVariable<?> variable) { + return ResolvableType.this.resolveVariable(variable); + } + + @Override + public Object getSource() { + return ResolvableType.this; + } + } + + + @SuppressWarnings("serial") + private static class TypeVariablesVariableResolver implements VariableResolver { + + private final TypeVariable<?>[] typeVariables; + + private final ResolvableType[] generics; + + public TypeVariablesVariableResolver(TypeVariable<?>[] typeVariables, ResolvableType[] generics) { + Assert.isTrue(typeVariables.length == generics.length, "Mismatched number of generics specified"); + this.typeVariables = typeVariables; + this.generics = generics; + } + + @Override + public ResolvableType resolveVariable(TypeVariable<?> variable) { + for (int i = 0; i < this.typeVariables.length; i++) { + if (SerializableTypeWrapper.unwrap(this.typeVariables[i]).equals( + SerializableTypeWrapper.unwrap(variable))) { + return this.generics[i]; + } + } + return null; + } + + @Override + public Object getSource() { + return this.generics; + } + } + + + /** + * Internal helper to handle bounds from {@link WildcardType}s. + */ + private static class WildcardBounds { + + private final Kind kind; + + private final ResolvableType[] bounds; + + /** + * Internal constructor to create a new {@link WildcardBounds} instance. + * @param kind the kind of bounds + * @param bounds the bounds + * @see #get(ResolvableType) + */ + public WildcardBounds(Kind kind, ResolvableType[] bounds) { + this.kind = kind; + this.bounds = bounds; + } + + /** + * Return {@code true} if this bounds is the same kind as the specified bounds. + */ + public boolean isSameKind(WildcardBounds bounds) { + return this.kind == bounds.kind; + } + + /** + * Return {@code true} if this bounds is assignable to all the specified types. + * @param types the types to test against + * @return {@code true} if this bounds is assignable to all types + */ + public boolean isAssignableFrom(ResolvableType... types) { + for (ResolvableType bound : this.bounds) { + for (ResolvableType type : types) { + if (!isAssignable(bound, type)) { + return false; + } + } + } + return true; + } + + private boolean isAssignable(ResolvableType source, ResolvableType from) { + return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source)); + } + + /** + * Return the underlying bounds. + */ + public ResolvableType[] getBounds() { + return this.bounds; + } + + /** + * Get a {@link WildcardBounds} instance for the specified type, returning + * {@code null} if the specified type cannot be resolved to a {@link WildcardType}. + * @param type the source type + * @return a {@link WildcardBounds} instance or {@code null} + */ + public static WildcardBounds get(ResolvableType type) { + ResolvableType resolveToWildcard = type; + while (!(resolveToWildcard.getType() instanceof WildcardType)) { + if (resolveToWildcard == NONE) { + return null; + } + resolveToWildcard = resolveToWildcard.resolveType(); + } + WildcardType wildcardType = (WildcardType) resolveToWildcard.type; + Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER); + Type[] bounds = boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds(); + ResolvableType[] resolvableBounds = new ResolvableType[bounds.length]; + for (int i = 0; i < bounds.length; i++) { + resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver); + } + return new WildcardBounds(boundsType, resolvableBounds); + } + + /** + * The various kinds of bounds. + */ + static enum Kind {UPPER, LOWER} + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java new file mode 100644 index 00000000..439f66b9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java @@ -0,0 +1,408 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; + +import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.ReflectionUtils; + +/** + * Internal utility class that can be used to obtain wrapped {@link Serializable} variants + * of {@link java.lang.reflect.Type}s. + * + * <p>{@link #forField(Field) Fields} or {@link #forMethodParameter(MethodParameter) + * MethodParameters} can be used as the root source for a serializable type. Alternatively + * the {@link #forGenericSuperclass(Class) superclass}, + * {@link #forGenericInterfaces(Class) interfaces} or {@link #forTypeParameters(Class) + * type parameters} or a regular {@link Class} can also be used as source. + * + * <p>The returned type will either be a {@link Class} or a serializable proxy of + * {@link GenericArrayType}, {@link ParameterizedType}, {@link TypeVariable} or + * {@link WildcardType}. With the exception of {@link Class} (which is final) calls to + * methods that return further {@link Type}s (for example + * {@link GenericArrayType#getGenericComponentType()}) will be automatically wrapped. + * + * @author Phillip Webb + * @since 4.0 + */ +abstract class SerializableTypeWrapper { + + private static final Class<?>[] SUPPORTED_SERIALIZABLE_TYPES = { + GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class}; + + private static final Method EQUALS_METHOD = ReflectionUtils.findMethod(Object.class, + "equals", Object.class); + + private static final Method GET_TYPE_PROVIDER_METHOD = ReflectionUtils.findMethod( + SerializableTypeProxy.class, "getTypeProvider"); + + private static final ConcurrentReferenceHashMap<Type, Type> cache = + new ConcurrentReferenceHashMap<Type, Type>(256); + + /** + * Return a {@link Serializable} variant of {@link Field#getGenericType()}. + */ + public static Type forField(Field field) { + Assert.notNull(field, "Field must not be null"); + return forTypeProvider(new FieldTypeProvider(field)); + } + + /** + * Return a {@link Serializable} variant of + * {@link MethodParameter#getGenericParameterType()}. + */ + public static Type forMethodParameter(MethodParameter methodParameter) { + return forTypeProvider(new MethodParameterTypeProvider(methodParameter)); + } + + /** + * Return a {@link Serializable} variant of {@link Class#getGenericSuperclass()}. + */ + @SuppressWarnings("serial") + public static Type forGenericSuperclass(final Class<?> type) { + return forTypeProvider(new DefaultTypeProvider() { + @Override + public Type getType() { + return type.getGenericSuperclass(); + } + }); + } + + /** + * Return a {@link Serializable} variant of {@link Class#getGenericInterfaces()}. + */ + @SuppressWarnings("serial") + public static Type[] forGenericInterfaces(final Class<?> type) { + Type[] result = new Type[type.getGenericInterfaces().length]; + for (int i = 0; i < result.length; i++) { + final int index = i; + result[i] = forTypeProvider(new DefaultTypeProvider() { + @Override + public Type getType() { + return type.getGenericInterfaces()[index]; + } + }); + } + return result; + } + + /** + * Return a {@link Serializable} variant of {@link Class#getTypeParameters()}. + */ + @SuppressWarnings("serial") + public static Type[] forTypeParameters(final Class<?> type) { + Type[] result = new Type[type.getTypeParameters().length]; + for (int i = 0; i < result.length; i++) { + final int index = i; + result[i] = forTypeProvider(new DefaultTypeProvider() { + @Override + public Type getType() { + return type.getTypeParameters()[index]; + } + }); + } + return result; + } + + /** + * Unwrap the given type, effectively returning the original non-serializable type. + * @param type the type to unwrap + * @return the original non-serializable type + */ + @SuppressWarnings("unchecked") + public static <T extends Type> T unwrap(T type) { + Type unwrapped = type; + while (unwrapped instanceof SerializableTypeProxy) { + unwrapped = ((SerializableTypeProxy) type).getTypeProvider().getType(); + } + return (T) unwrapped; + } + + /** + * Return a {@link Serializable} {@link Type} backed by a {@link TypeProvider} . + */ + static Type forTypeProvider(final TypeProvider provider) { + Assert.notNull(provider, "Provider must not be null"); + if (provider.getType() instanceof Serializable || provider.getType() == null) { + return provider.getType(); + } + Type cached = cache.get(provider.getType()); + if (cached != null) { + return cached; + } + for (Class<?> type : SUPPORTED_SERIALIZABLE_TYPES) { + if (type.isAssignableFrom(provider.getType().getClass())) { + ClassLoader classLoader = provider.getClass().getClassLoader(); + Class<?>[] interfaces = new Class<?>[] { type, + SerializableTypeProxy.class, Serializable.class }; + InvocationHandler handler = new TypeProxyInvocationHandler(provider); + cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler); + cache.put(provider.getType(), cached); + return cached; + } + } + throw new IllegalArgumentException("Unsupported Type class " + provider.getType().getClass().getName()); + } + + + /** + * Additional interface implemented by the type proxy. + */ + static interface SerializableTypeProxy { + + /** + * Return the underlying type provider. + */ + TypeProvider getTypeProvider(); + + } + + + /** + * A {@link Serializable} interface providing access to a {@link Type}. + */ + static interface TypeProvider extends Serializable { + + /** + * Return the (possibly non {@link Serializable}) {@link Type}. + */ + Type getType(); + + /** + * Return the source of the type or {@code null}. + */ + Object getSource(); + } + + + /** + * Default implementation of {@link TypeProvider} with a {@code null} source. + */ + @SuppressWarnings("serial") + private static abstract class DefaultTypeProvider implements TypeProvider { + + @Override + public Object getSource() { + return null; + } + + } + + + /** + * {@link Serializable} {@link InvocationHandler} used by the Proxied {@link Type}. + * Provides serialization support and enhances any methods that return {@code Type} + * or {@code Type[]}. + */ + @SuppressWarnings("serial") + private static class TypeProxyInvocationHandler implements InvocationHandler, Serializable { + + private final TypeProvider provider; + + public TypeProxyInvocationHandler(TypeProvider provider) { + this.provider = provider; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (GET_TYPE_PROVIDER_METHOD.equals(method)) { + return this.provider; + } + if (EQUALS_METHOD.equals(method)) { + Object other = args[0]; + // Unwrap proxies for speed + if (other instanceof Type) { + other = unwrap((Type) other); + } + return this.provider.getType().equals(other); + } + if (Type.class.equals(method.getReturnType()) && args == null) { + return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1)); + } + if (Type[].class.equals(method.getReturnType()) && args == null) { + Type[] result = new Type[((Type[]) method.invoke(this.provider.getType(), args)).length]; + for (int i = 0; i < result.length; i++) { + result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i)); + } + return result; + } + try { + return method.invoke(this.provider.getType(), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + + + /** + * {@link TypeProvider} for {@link Type}s obtained from a {@link Field}. + */ + @SuppressWarnings("serial") + static class FieldTypeProvider implements TypeProvider { + + private final String fieldName; + + private final Class<?> declaringClass; + + private transient Field field; + + public FieldTypeProvider(Field field) { + this.fieldName = field.getName(); + this.declaringClass = field.getDeclaringClass(); + this.field = field; + } + + @Override + public Type getType() { + return this.field.getGenericType(); + } + + @Override + public Object getSource() { + return this.field; + } + + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + try { + this.field = this.declaringClass.getDeclaredField(this.fieldName); + } + catch (Throwable ex) { + throw new IllegalStateException("Could not find original class structure", ex); + } + } + } + + + /** + * {@link TypeProvider} for {@link Type}s obtained from a {@link MethodParameter}. + */ + @SuppressWarnings("serial") + static class MethodParameterTypeProvider implements TypeProvider { + + private final String methodName; + + private final Class<?>[] parameterTypes; + + private final Class<?> declaringClass; + + private final int parameterIndex; + + private transient MethodParameter methodParameter; + + public MethodParameterTypeProvider(MethodParameter methodParameter) { + if (methodParameter.getMethod() != null) { + this.methodName = methodParameter.getMethod().getName(); + this.parameterTypes = methodParameter.getMethod().getParameterTypes(); + } + else { + this.methodName = null; + this.parameterTypes = methodParameter.getConstructor().getParameterTypes(); + } + this.declaringClass = methodParameter.getDeclaringClass(); + this.parameterIndex = methodParameter.getParameterIndex(); + this.methodParameter = methodParameter; + } + + + @Override + public Type getType() { + return this.methodParameter.getGenericParameterType(); + } + + @Override + public Object getSource() { + return this.methodParameter; + } + + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + try { + if (this.methodName != null) { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex); + } + else { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex); + } + } + catch (Throwable ex) { + throw new IllegalStateException("Could not find original class structure", ex); + } + } + } + + + /** + * {@link TypeProvider} for {@link Type}s obtained by invoking a no-arg method. + */ + @SuppressWarnings("serial") + static class MethodInvokeTypeProvider implements TypeProvider { + + private final TypeProvider provider; + + private final String methodName; + + private final int index; + + private transient Object result; + + public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) { + this.provider = provider; + this.methodName = method.getName(); + this.index = index; + this.result = ReflectionUtils.invokeMethod(method, provider.getType()); + } + + @Override + public Type getType() { + if (this.result instanceof Type || this.result == null) { + return (Type) this.result; + } + return ((Type[])this.result)[this.index]; + } + + @Override + public Object getSource() { + return null; + } + + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + Method method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName); + this.result = ReflectionUtils.invokeMethod(method, this.provider.getType()); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java index 24b33f76..82fe6615 100644 --- a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java @@ -41,6 +41,7 @@ public class SimpleAliasRegistry implements AliasRegistry { private final Map<String, String> aliasMap = new ConcurrentHashMap<String, String>(16); + @Override public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); @@ -68,6 +69,7 @@ public class SimpleAliasRegistry implements AliasRegistry { return true; } + @Override public void removeAlias(String alias) { String name = this.aliasMap.remove(alias); if (name == null) { @@ -75,10 +77,12 @@ public class SimpleAliasRegistry implements AliasRegistry { } } + @Override public boolean isAlias(String name) { return this.aliasMap.containsKey(name); } + @Override public String[] getAliases(String name) { List<String> result = new ArrayList<String>(); synchronized (this.aliasMap) { diff --git a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java index c036f27a..a0f72d0b 100644 --- a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java +++ b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java @@ -38,6 +38,6 @@ public interface SmartClassLoader { * @return whether the class should be expected to appear in a reloaded * version (with a different {@code Class} object) later on */ - boolean isClassReloadable(Class clazz); + boolean isClassReloadable(Class<?> clazz); } diff --git a/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java new file mode 100644 index 00000000..d8a39bd4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +/** + * {@link ParameterNameDiscoverer} implementation which uses JDK 8's reflection facilities + * for introspecting parameter names (based on the "-parameters" compiler flag). + * + * @author Juergen Hoeller + * @since 4.0 + * @see java.lang.reflect.Parameter#getName() + */ +public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer { + + @Override + public String[] getParameterNames(Method method) { + Parameter[] parameters = method.getParameters(); + String[] parameterNames = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + Parameter param = parameters[i]; + if (!param.isNamePresent()) { + return null; + } + parameterNames[i] = param.getName(); + } + return parameterNames; + } + + @Override + public String[] getParameterNames(Constructor<?> ctor) { + Parameter[] parameters = ctor.getParameters(); + String[] parameterNames = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + Parameter param = parameters[i]; + if (!param.isNamePresent()) { + return null; + } + parameterNames[i] = param.getName(); + } + return parameterNames; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java new file mode 100644 index 00000000..af84f7b5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -0,0 +1,253 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Utility class used to collect all annotation values including those declared on + * meta-annotations. + * + * @author Phillip Webb + * @author Juergen Hoeller + * @author Sam Brannen + * @since 4.0 + */ +public class AnnotatedElementUtils { + + public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) { + final Set<String> types = new LinkedHashSet<String>(); + process(element, annotationType, false, new Processor<Object>() { + @Override + public Object process(Annotation annotation, int metaDepth) { + if (metaDepth > 0) { + types.add(annotation.annotationType().getName()); + } + return null; + } + @Override + public void postProcess(Annotation annotation, Object result) { + } + }); + return (types.isEmpty() ? null : types); + } + + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) { + return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() { + @Override + public Boolean process(Annotation annotation, int metaDepth) { + if (metaDepth > 0) { + return Boolean.TRUE; + } + return null; + } + @Override + public void postProcess(Annotation annotation, Boolean result) { + } + })); + } + + public static boolean isAnnotated(AnnotatedElement element, String annotationType) { + return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() { + @Override + public Boolean process(Annotation annotation, int metaDepth) { + return Boolean.TRUE; + } + @Override + public void postProcess(Annotation annotation, Boolean result) { + } + })); + } + + public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) { + return getAnnotationAttributes(element, annotationType, false, false); + } + + public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, + final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + + return process(element, annotationType, false, new Processor<AnnotationAttributes>() { + @Override + public AnnotationAttributes process(Annotation annotation, int metaDepth) { + return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap); + } + @Override + public void postProcess(Annotation annotation, AnnotationAttributes result) { + for (String key : result.keySet()) { + if (!AnnotationUtils.VALUE.equals(key)) { + Object value = AnnotationUtils.getValue(annotation, key); + if (value != null) { + result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); + } + } + } + } + }); + } + + public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationType) { + return getAllAnnotationAttributes(element, annotationType, false, false); + } + + public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, + final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + + final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>(); + process(element, annotationType, false, new Processor<Void>() { + @Override + public Void process(Annotation annotation, int metaDepth) { + if (annotation.annotationType().getName().equals(annotationType)) { + for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes( + annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) { + attributes.add(entry.getKey(), entry.getValue()); + } + } + return null; + } + @Override + public void postProcess(Annotation annotation, Void result) { + for (String key : attributes.keySet()) { + if (!AnnotationUtils.VALUE.equals(key)) { + Object value = AnnotationUtils.getValue(annotation, key); + if (value != null) { + attributes.add(key, value); + } + } + } + } + }); + return (attributes.isEmpty() ? null : attributes); + } + + /** + * Process all annotations of the specified {@code annotationType} and + * recursively all meta-annotations on the specified {@code element}. + * <p>If the {@code traverseClassHierarchy} flag is {@code true} and the sought + * annotation is neither <em>directly present</em> on the given element nor + * present on the given element as a meta-annotation, then the algorithm will + * recursively search through the class hierarchy of the given element. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param traverseClassHierarchy whether or not to traverse up the class + * hierarchy recursively + * @param processor the processor to delegate to + * @return the result of the processor + */ + private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy, + Processor<T> processor) { + + try { + return doProcess(element, annotationType, traverseClassHierarchy, processor, + new HashSet<AnnotatedElement>(), 0); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect annotations: " + element, ex); + } + } + + /** + * Perform the search algorithm for the {@link #process} method, avoiding + * endless recursion by tracking which annotated elements have already been + * <em>visited</em>. + * <p>The {@code metaDepth} parameter represents the depth of the annotation + * relative to the initial element. For example, an annotation that is + * <em>present</em> on the element will have a depth of 0; a meta-annotation + * will have a depth of 1; and a meta-meta-annotation will have a depth of 2. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param traverseClassHierarchy whether or not to traverse up the class + * hierarchy recursively + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the depth of the annotation relative to the initial element + * @return the result of the processor + */ + private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy, + Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) { + + if (visited.add(element)) { + Annotation[] annotations = + (traverseClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations()); + for (Annotation annotation : annotations) { + if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) { + T result = processor.process(annotation, metaDepth); + if (result != null) { + return result; + } + result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy, + processor, visited, metaDepth + 1); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + for (Annotation annotation : annotations) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { + T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy, + processor, visited, metaDepth); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + if (traverseClassHierarchy && element instanceof Class) { + Class<?> superclass = ((Class<?>) element).getSuperclass(); + if (superclass != null && !superclass.equals(Object.class)) { + T result = doProcess(superclass, annotationType, true, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + } + return null; + } + + + /** + * Callback interface used to process an annotation. + * @param <T> the result type + */ + private static interface Processor<T> { + + /** + * Called to process the annotation. + * <p>The {@code metaDepth} parameter represents the depth of the + * annotation relative to the initial element. For example, an annotation + * that is <em>present</em> on the element will have a depth of 0; a + * meta-annotation will have a depth of 1; and a meta-meta-annotation + * will have a depth of 2. + * @param annotation the annotation to process + * @param metaDepth the depth of the annotation relative to the initial element + * @return the result of the processing, or {@code null} to continue + */ + T process(Annotation annotation, int metaDepth); + + void postProcess(Annotation annotation, T result); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index 40041e8d..090621d8 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -16,8 +16,7 @@ package org.springframework.core.annotation; -import static java.lang.String.format; - +import java.lang.reflect.Array; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -27,10 +26,9 @@ import org.springframework.util.StringUtils; /** * {@link LinkedHashMap} subclass representing annotation attribute key/value pairs - * as read by Spring's reflection- or ASM-based {@link - * org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations. - * Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well - * as convenience methods for looking up annotation attributes in a type-safe fashion. + * as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata} + * implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code + * as well as convenience methods for looking up annotation attributes in a type-safe fashion. * * @author Chris Beams * @since 3.1.1 @@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { super(map); } - /** - * Return an {@link AnnotationAttributes} instance based on the given map; if the map - * is already an {@code AnnotationAttributes} instance, it is casted and returned - * immediately without creating any new instance; otherwise create a new instance by - * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor. - * @param map original source of annotation attribute key/value pairs - */ - public static AnnotationAttributes fromMap(Map<String, Object> map) { - if (map == null) { - return null; - } - - if (map instanceof AnnotationAttributes) { - return (AnnotationAttributes) map; - } - - return new AnnotationAttributes(map); - } public String getString(String attributeName) { return doGet(attributeName, String.class); @@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @SuppressWarnings("unchecked") public <N extends Number> N getNumber(String attributeName) { - return (N) doGet(attributeName, Integer.class); + return (N) doGet(attributeName, Number.class); } @SuppressWarnings("unchecked") @@ -124,14 +104,24 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { @SuppressWarnings("unchecked") private <T> T doGet(String attributeName, Class<T> expectedType) { Assert.hasText(attributeName, "attributeName must not be null or empty"); - Object value = this.get(attributeName); - Assert.notNull(value, format("Attribute '%s' not found", attributeName)); - Assert.isAssignable(expectedType, value.getClass(), - format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ", + Object value = get(attributeName); + Assert.notNull(value, String.format("Attribute '%s' not found", attributeName)); + if (!expectedType.isInstance(value)) { + if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) { + Object arrayValue = Array.newInstance(expectedType.getComponentType(), 1); + Array.set(arrayValue, 0, value); + value = arrayValue; + } + else { + throw new IllegalArgumentException( + String.format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ", attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName())); + } + } return (T) value; } + @Override public String toString() { Iterator<Map.Entry<String, Object>> entries = entrySet().iterator(); StringBuilder sb = new StringBuilder("{"); @@ -155,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> { } return String.valueOf(value); } + + + /** + * Return an {@link AnnotationAttributes} instance based on the given map; if the map + * is already an {@code AnnotationAttributes} instance, it is casted and returned + * immediately without creating any new instance; otherwise create a new instance by + * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor. + * @param map original source of annotation attribute key/value pairs + */ + public static AnnotationAttributes fromMap(Map<String, Object> map) { + if (map == null) { + return null; + } + if (map instanceof AnnotationAttributes) { + return (AnnotationAttributes) map; + } + return new AnnotationAttributes(map); + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java index 19ba5fec..f4a83b8f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java @@ -50,7 +50,7 @@ public class AnnotationAwareOrderComparator extends OrderComparator { return ((Ordered) obj).getOrder(); } if (obj != null) { - Class<?> clazz = (obj instanceof Class ? (Class) obj : obj.getClass()); + Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass()); Order order = AnnotationUtils.findAnnotation(clazz, Order.class); if (order != null) { return order.value(); @@ -86,4 +86,21 @@ public class AnnotationAwareOrderComparator extends OrderComparator { } } + /** + * Sort the given array or List with a default AnnotationAwareOrderComparator, + * if necessary. Simply skips sorting when given any other value. + * <p>Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param value the array or List to sort + * @see java.util.Arrays#sort(Object[], java.util.Comparator) + */ + public static void sortIfNecessary(Object value) { + if (value instanceof Object[]) { + sort((Object[]) value); + } + else if (value instanceof List) { + sort((List<?>) value); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 3b4def63..45d7c48c 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -19,31 +19,44 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.BridgeMethodResolver; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** - * General utility methods for working with annotations, handling bridge methods (which the compiler - * generates for generic declarations) as well as super methods (for optional "annotation inheritance"). - * Note that none of this is provided by the JDK's introspection facilities themselves. + * General utility methods for working with annotations, handling bridge methods + * (which the compiler generates for generic declarations) as well as super methods + * (for optional "annotation inheritance"). Note that none of this is + * provided by the JDK's introspection facilities themselves. * - * <p>As a general rule for runtime-retained annotations (e.g. for transaction control, authorization or service - * exposure), always use the lookup methods on this class (e.g., {@link #findAnnotation(Method, Class)}, {@link - * #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) instead of the plain annotation lookup - * methods in the JDK. You can still explicitly choose between lookup on the given class level only ({@link - * #getAnnotation(Method, Class)}) and lookup in the entire inheritance hierarchy of the given method ({@link - * #findAnnotation(Method, Class)}). + * <p>As a general rule for runtime-retained annotations (e.g. for transaction + * control, authorization, or service exposure), always use the lookup methods + * on this class (e.g., {@link #findAnnotation(Method, Class)}, + * {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) + * instead of the plain annotation lookup methods in the JDK. You can still + * explicitly choose between a <em>get</em> lookup on the given class level only + * ({@link #getAnnotation(Method, Class)}) and a <em>find</em> lookup in the entire + * inheritance hierarchy of the given method ({@link #findAnnotation(Method, Class)}). * * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen * @author Mark Fisher * @author Chris Beams + * @author Phillip Webb * @since 2.0 * @see java.lang.reflect.Method#getAnnotations() * @see java.lang.reflect.Method#getAnnotation(Class) @@ -51,42 +64,100 @@ import org.springframework.util.ReflectionUtils; public abstract class AnnotationUtils { /** The attribute name for annotations with a single element */ - static final String VALUE = "value"; + public static final String VALUE = "value"; + private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>(); + private static transient Log logger; + + + /** + * Get a single {@link Annotation} of {@code annotationType} from the supplied + * annotation: either the given annotation itself or a meta-annotation thereof. + * @param ann the Annotation to check + * @param annotationType the annotation type to look for, both locally and as a meta-annotation + * @return the matching annotation, or {@code null} if none found + * @since 4.0 + */ + @SuppressWarnings("unchecked") + public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> annotationType) { + if (annotationType.isInstance(ann)) { + return (T) ann; + } + try { + return ann.annotationType().getAnnotation(annotationType); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(ann.annotationType(), ex); + return null; + } + } /** * Get a single {@link Annotation} of {@code annotationType} from the supplied * Method, Constructor or Field. Meta-annotations will be searched if the annotation * is not declared locally on the supplied element. - * @param ae the Method, Constructor or Field from which to get the annotation + * @param annotatedElement the Method, Constructor or Field from which to get the annotation * @param annotationType the annotation type to look for, both locally and as a meta-annotation * @return the matching annotation, or {@code null} if none found * @since 3.1 */ - public static <T extends Annotation> T getAnnotation(AnnotatedElement ae, Class<T> annotationType) { - T ann = ae.getAnnotation(annotationType); - if (ann == null) { - for (Annotation metaAnn : ae.getAnnotations()) { - ann = metaAnn.annotationType().getAnnotation(annotationType); - if (ann != null) { - break; + public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) { + try { + T ann = annotatedElement.getAnnotation(annotationType); + if (ann == null) { + for (Annotation metaAnn : annotatedElement.getAnnotations()) { + ann = metaAnn.annotationType().getAnnotation(annotationType); + if (ann != null) { + break; + } } } + return ann; + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(annotatedElement, ex); + return null; + } + } + + /** + * Get all {@link Annotation Annotations} from the supplied Method, Constructor or Field. + * @param annotatedElement the Method, Constructor or Field to retrieve annotations from + * @return the annotations found, or {@code null} if not resolvable (e.g. because nested + * Class values in annotation attributes failed to resolve at runtime) + * @since 4.0.8 + */ + public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) { + try { + return annotatedElement.getAnnotations(); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(annotatedElement, ex); + return null; } - return ann; } /** * Get all {@link Annotation Annotations} from the supplied {@link Method}. * <p>Correctly handles bridge {@link Method Methods} generated by the compiler. - * @param method the method to look for annotations on + * @param method the Method to retrieve annotations from * @return the annotations found * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) */ public static Annotation[] getAnnotations(Method method) { - return BridgeMethodResolver.findBridgedMethod(method).getAnnotations(); + try { + return BridgeMethodResolver.findBridgedMethod(method).getAnnotations(); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(method, ex); + return null; + } } /** @@ -99,22 +170,61 @@ public abstract class AnnotationUtils { */ public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) { Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - A ann = resolvedMethod.getAnnotation(annotationType); - if (ann == null) { - for (Annotation metaAnn : resolvedMethod.getAnnotations()) { - ann = metaAnn.annotationType().getAnnotation(annotationType); - if (ann != null) { - break; - } + return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); + } + + /** + * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the + * supplied {@link Method}. Deals with both a single direct annotation and repeating + * annotations nested within a containing annotation. + * <p>Correctly handles bridge {@link Method Methods} generated by the compiler. + * @param method the method to look for annotations on + * @param containerAnnotationType the class of the container that holds the annotations + * @param annotationType the annotation type to look for + * @return the annotations found + * @since 4.0 + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + */ + public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method, + Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) { + + Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); + return getRepeatableAnnotation((AnnotatedElement) resolvedMethod, containerAnnotationType, annotationType); + } + + /** + * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the + * supplied {@link AnnotatedElement}. Deals with both a single direct annotation and + * repeating annotations nested within a containing annotation. + * <p>Correctly handles bridge {@link Method Methods} generated by the compiler. + * @param annotatedElement the element to look for annotations on + * @param containerAnnotationType the class of the container that holds the annotations + * @param annotationType the annotation type to look for + * @return the annotations found + * @since 4.0 + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + */ + public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement, + Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) { + + try { + if (annotatedElement.getAnnotations().length > 0) { + return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement); } } - return ann; + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(annotatedElement, ex); + } + return Collections.emptySet(); } /** - * Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method}, - * traversing its super methods if no annotation can be found on the given method itself. - * <p>Annotations on methods are not inherited by default, so we need to handle this explicitly. + * Find a single {@link Annotation} of {@code annotationType} from the supplied + * {@link Method}, traversing its super methods (i.e., from superclasses and + * interfaces) if no annotation can be found on the given method itself. + * <p>Annotations on methods are not inherited by default, so we need to handle + * this explicitly. * @param method the method to look for annotations on * @param annotationType the annotation type to look for * @return the annotation found, or {@code null} if none @@ -171,9 +281,15 @@ public abstract class AnnotationUtils { } boolean found = false; for (Method ifcMethod : iface.getMethods()) { - if (ifcMethod.getAnnotations().length > 0) { - found = true; - break; + try { + if (ifcMethod.getAnnotations().length > 0) { + found = true; + break; + } + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(ifcMethod, ex); } } annotatedInterfaceCache.put(iface, found); @@ -182,45 +298,82 @@ public abstract class AnnotationUtils { } /** - * Find a single {@link Annotation} of {@code annotationType} from the supplied {@link Class}, - * traversing its interfaces and superclasses if no annotation can be found on the given class itself. - * <p>This method explicitly handles class-level annotations which are not declared as - * {@link java.lang.annotation.Inherited inherited} <i>as well as annotations on interfaces</i>. - * <p>The algorithm operates as follows: Searches for an annotation on the given class and returns - * it if found. Else searches all interfaces that the given class declares, returning the annotation - * from the first matching candidate, if any. Else proceeds with introspection of the superclass - * of the given class, checking the superclass itself; if no annotation found there, proceeds - * with the interfaces that the superclass declares. Recursing up through the entire superclass - * hierarchy if no match is found. + * Find a single {@link Annotation} of {@code annotationType} on the + * supplied {@link Class}, traversing its interfaces, annotations, and + * superclasses if the annotation is not <em>present</em> on the given class + * itself. + * <p>This method explicitly handles class-level annotations which are not + * declared as {@link java.lang.annotation.Inherited inherited} <em>as well + * as meta-annotations and annotations on interfaces</em>. + * <p>The algorithm operates as follows: + * <ol> + * <li>Search for the annotation on the given class and return it if found. + * <li>Recursively search through all interfaces that the given class declares. + * <li>Recursively search through all annotations that the given class declares. + * <li>Recursively search through the superclass hierarchy of the given class. + * </ol> + * <p>Note: in this context, the term <em>recursively</em> means that the search + * process continues by returning to step #1 with the current interface, + * annotation, or superclass as the class to look for annotations on. * @param clazz the class to look for annotations on - * @param annotationType the annotation type to look for - * @return the annotation found, or {@code null} if none found + * @param annotationType the type of annotation to look for + * @return the annotation if found, or {@code null} if not found */ public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) { + return findAnnotation(clazz, annotationType, new HashSet<Annotation>()); + } + + /** + * Perform the search algorithm for {@link #findAnnotation(Class, Class)}, + * avoiding endless recursion by tracking which annotations have already + * been <em>visited</em>. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @param visited the set of annotations that have already been visited + * @return the annotation if found, or {@code null} if not found + */ + private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) { Assert.notNull(clazz, "Class must not be null"); - A annotation = clazz.getAnnotation(annotationType); - if (annotation != null) { - return annotation; + + if (isAnnotationDeclaredLocally(annotationType, clazz)) { + try { + return clazz.getAnnotation(annotationType); + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(clazz, ex); + return null; + } } + for (Class<?> ifc : clazz.getInterfaces()) { - annotation = findAnnotation(ifc, annotationType); + A annotation = findAnnotation(ifc, annotationType, visited); if (annotation != null) { return annotation; } } - if (!Annotation.class.isAssignableFrom(clazz)) { - for (Annotation ann : clazz.getAnnotations()) { - annotation = findAnnotation(ann.annotationType(), annotationType); - if (annotation != null) { - return annotation; + + try { + for (Annotation ann : clazz.getDeclaredAnnotations()) { + if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { + A annotation = findAnnotation(ann.annotationType(), annotationType, visited); + if (annotation != null) { + return annotation; + } } } } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(clazz, ex); + return null; + } + Class<?> superclass = clazz.getSuperclass(); if (superclass == null || superclass.equals(Object.class)) { return null; } - return findAnnotation(superclass, annotationType); + return findAnnotation(superclass, annotationType, visited); } /** @@ -312,12 +465,18 @@ public abstract class AnnotationUtils { Assert.notNull(annotationType, "Annotation type must not be null"); Assert.notNull(clazz, "Class must not be null"); boolean declaredLocally = false; - for (Annotation annotation : clazz.getDeclaredAnnotations()) { - if (annotation.annotationType().equals(annotationType)) { - declaredLocally = true; - break; + try { + for (Annotation ann : clazz.getDeclaredAnnotations()) { + if (ann.annotationType().equals(annotationType)) { + declaredLocally = true; + break; + } } } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + logIntrospectionFailure(clazz, ex); + } return declaredLocally; } @@ -343,11 +502,21 @@ public abstract class AnnotationUtils { } /** - * Retrieve the given annotation's attributes as a Map, preserving all attribute types - * as-is. - * <p>Note: As of Spring 3.1.1, the returned map is actually an - * {@link AnnotationAttributes} instance, however the Map signature of this method has - * been preserved for binary compatibility. + * Determine if the supplied {@link Annotation} is defined in the core JDK + * {@code java.lang.annotation} package. + * @param annotation the annotation to check (never {@code null}) + * @return {@code true} if the annotation is in the {@code java.lang.annotation} package + */ + public static boolean isInJavaLangAnnotationPackage(Annotation annotation) { + Assert.notNull(annotation, "Annotation must not be null"); + return annotation.annotationType().getName().startsWith("java.lang.annotation"); + } + + /** + * Retrieve the given annotation's attributes as a {@link Map}, preserving all + * attribute types as-is. + * <p>Note: This method actually returns an {@link AnnotationAttributes} instance. + * However, the {@code Map} signature has been preserved for binary compatibility. * @param annotation the annotation to retrieve the attributes for * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values @@ -357,16 +526,15 @@ public abstract class AnnotationUtils { } /** - * Retrieve the given annotation's attributes as a Map. Equivalent to calling - * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with + * Retrieve the given annotation's attributes as a {@link Map}. Equivalent to + * calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with * the {@code nestedAnnotationsAsMap} parameter set to {@code false}. - * <p>Note: As of Spring 3.1.1, the returned map is actually an - * {@link AnnotationAttributes} instance, however the Map signature of this method has - * been preserved for binary compatibility. + * <p>Note: This method actually returns an {@link AnnotationAttributes} instance. + * However, the {@code Map} signature has been preserved for binary compatibility. * @param annotation the annotation to retrieve the attributes for * @param classValuesAsString whether to turn Class references into Strings (for - * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to - * preserve them as Class references + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} + * or to preserve them as Class references * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values */ @@ -376,13 +544,13 @@ public abstract class AnnotationUtils { /** * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} - * map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation - * reading capabilities on par with that of the reflection-based - * {@link org.springframework.core.type.StandardAnnotationMetadata}. + * map structure. + * <p>This method provides fully recursive annotation reading capabilities on par with + * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}. * @param annotation the annotation to retrieve the attributes for * @param classValuesAsString whether to turn Class references into Strings (for - * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to - * preserve them as Class references + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} + * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as @@ -400,34 +568,7 @@ public abstract class AnnotationUtils { if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) { try { Object value = method.invoke(annotation); - if (classValuesAsString) { - if (value instanceof Class) { - value = ((Class<?>) value).getName(); - } - else if (value instanceof Class[]) { - Class<?>[] clazzArray = (Class[]) value; - String[] newValue = new String[clazzArray.length]; - for (int i = 0; i < clazzArray.length; i++) { - newValue[i] = clazzArray[i].getName(); - } - value = newValue; - } - } - if (nestedAnnotationsAsMap && value instanceof Annotation) { - attrs.put(method.getName(), - getAnnotationAttributes((Annotation) value, classValuesAsString, true)); - } - else if (nestedAnnotationsAsMap && value instanceof Annotation[]) { - Annotation[] realAnnotations = (Annotation[]) value; - AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; - for (int i = 0; i < realAnnotations.length; i++) { - mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true); - } - attrs.put(method.getName(), mappedAnnotations); - } - else { - attrs.put(method.getName(), value); - } + attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); } catch (Exception ex) { throw new IllegalStateException("Could not obtain annotation attribute values", ex); @@ -438,6 +579,48 @@ public abstract class AnnotationUtils { } /** + * Adapt the given value according to the given class and nested annotation settings. + * @param value the annotation attribute value + * @param classValuesAsString whether to turn Class references into Strings (for + * compatibility with {@link org.springframework.core.type.AnnotationMetadata} + * or to preserve them as Class references + * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into + * {@link AnnotationAttributes} maps (for compatibility with + * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as + * Annotation instances + * @return the adapted value, or the original value if no adaptation is needed + */ + static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + if (classValuesAsString) { + if (value instanceof Class) { + value = ((Class<?>) value).getName(); + } + else if (value instanceof Class[]) { + Class<?>[] clazzArray = (Class[]) value; + String[] newValue = new String[clazzArray.length]; + for (int i = 0; i < clazzArray.length; i++) { + newValue[i] = clazzArray[i].getName(); + } + value = newValue; + } + } + if (nestedAnnotationsAsMap && value instanceof Annotation) { + return getAnnotationAttributes((Annotation) value, classValuesAsString, true); + } + else if (nestedAnnotationsAsMap && value instanceof Annotation[]) { + Annotation[] realAnnotations = (Annotation[]) value; + AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; + for (int i = 0; i < realAnnotations.length; i++) { + mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true); + } + return mappedAnnotations; + } + else { + return value; + } + } + + /** * Retrieve the <em>value</em> of the {@code "value"} attribute of a * single-element Annotation, given an annotation instance. * @param annotation the annotation instance from which to retrieve the value @@ -449,7 +632,7 @@ public abstract class AnnotationUtils { } /** - * Retrieve the <em>value</em> of a named Annotation attribute, given an annotation instance. + * Retrieve the <em>value</em> of a named attribute, given an annotation instance. * @param annotation the annotation instance from which to retrieve the value * @param attributeName the name of the attribute value to retrieve * @return the attribute value, or {@code null} if not found @@ -478,7 +661,7 @@ public abstract class AnnotationUtils { } /** - * Retrieve the <em>default value</em> of a named Annotation attribute, given an annotation instance. + * Retrieve the <em>default value</em> of a named attribute, given an annotation instance. * @param annotation the annotation instance from which to retrieve the default value * @param attributeName the name of the attribute value to retrieve * @return the default value of the named attribute, or {@code null} if not found @@ -500,7 +683,8 @@ public abstract class AnnotationUtils { } /** - * Retrieve the <em>default value</em> of a named Annotation attribute, given the {@link Class annotation type}. + * Retrieve the <em>default value</em> of a named attribute, given the + * {@link Class annotation type}. * @param annotationType the <em>annotation type</em> for which the default value should be retrieved * @param attributeName the name of the attribute value to retrieve. * @return the default value of the named attribute, or {@code null} if not found @@ -515,4 +699,68 @@ public abstract class AnnotationUtils { } } + + private static void logIntrospectionFailure(AnnotatedElement annotatedElement, Exception ex) { + Log loggerToUse = logger; + if (loggerToUse == null) { + loggerToUse = LogFactory.getLog(AnnotationUtils.class); + logger = loggerToUse; + } + if (loggerToUse.isInfoEnabled()) { + loggerToUse.info("Failed to introspect annotations on [" + annotatedElement + "]: " + ex); + } + } + + + private static class AnnotationCollector<A extends Annotation> { + + private final Class<? extends Annotation> containerAnnotationType; + + private final Class<A> annotationType; + + private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>(); + + private final Set<A> result = new LinkedHashSet<A>(); + + public AnnotationCollector(Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) { + this.containerAnnotationType = containerAnnotationType; + this.annotationType = annotationType; + } + + public Set<A> getResult(AnnotatedElement element) { + process(element); + return Collections.unmodifiableSet(this.result); + } + + @SuppressWarnings("unchecked") + private void process(AnnotatedElement annotatedElement) { + if (this.visited.add(annotatedElement)) { + for (Annotation ann : annotatedElement.getAnnotations()) { + if (ObjectUtils.nullSafeEquals(this.annotationType, ann.annotationType())) { + this.result.add((A) ann); + } + else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, ann.annotationType())) { + this.result.addAll(getValue(ann)); + } + else if (!isInJavaLangAnnotationPackage(ann)) { + process(ann.annotationType()); + } + } + } + } + + @SuppressWarnings("unchecked") + private List<A> getValue(Annotation annotation) { + try { + Method method = annotation.annotationType().getDeclaredMethod("value"); + ReflectionUtils.makeAccessible(method); + return Arrays.asList((A[]) method.invoke(annotation)); + } + catch (Exception ex) { + // Unable to read value from repeating annotation container -> ignore it. + return Collections.emptyList(); + } + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java deleted file mode 100644 index 86995f32..00000000 --- a/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * 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 org.springframework.core.convert; - -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.Map; - -import org.springframework.util.Assert; - -/** - * @author Keith Donald - * @since 3.1 - */ -abstract class AbstractDescriptor { - - private final Class<?> type; - - - protected AbstractDescriptor(Class<?> type) { - Assert.notNull(type, "Type must not be null"); - this.type = type; - } - - - public Class<?> getType() { - return this.type; - } - - public TypeDescriptor getElementTypeDescriptor() { - if (isCollection()) { - Class<?> elementType = resolveCollectionElementType(); - return (elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null); - } - else if (isArray()) { - Class<?> elementType = getType().getComponentType(); - return new TypeDescriptor(nested(elementType, 0)); - } - else { - return null; - } - } - - public TypeDescriptor getMapKeyTypeDescriptor() { - if (isMap()) { - Class<?> keyType = resolveMapKeyType(); - return keyType != null ? new TypeDescriptor(nested(keyType, 0)) : null; - } - else { - return null; - } - } - - public TypeDescriptor getMapValueTypeDescriptor() { - if (isMap()) { - Class<?> valueType = resolveMapValueType(); - return valueType != null ? new TypeDescriptor(nested(valueType, 1)) : null; - } - else { - return null; - } - } - - public AbstractDescriptor nested() { - if (isCollection()) { - Class<?> elementType = resolveCollectionElementType(); - return (elementType != null ? nested(elementType, 0) : null); - } - else if (isArray()) { - return nested(getType().getComponentType(), 0); - } - else if (isMap()) { - Class<?> mapValueType = resolveMapValueType(); - return (mapValueType != null ? nested(mapValueType, 1) : null); - } - else if (Object.class.equals(getType())) { - // could be a collection type but we don't know about its element type, - // so let's just assume there is an element type of type Object - return this; - } - else { - throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types"); - } - } - - - // subclassing hooks - - public abstract Annotation[] getAnnotations(); - - protected abstract Class<?> resolveCollectionElementType(); - - protected abstract Class<?> resolveMapKeyType(); - - protected abstract Class<?> resolveMapValueType(); - - protected abstract AbstractDescriptor nested(Class<?> type, int typeIndex); - - - // internal helpers - - private boolean isCollection() { - return Collection.class.isAssignableFrom(getType()); - } - - private boolean isArray() { - return getType().isArray(); - } - - private boolean isMap() { - return Map.class.isAssignableFrom(getType()); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java deleted file mode 100644 index 8be6b0e2..00000000 --- a/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * 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 org.springframework.core.convert; - -import java.lang.annotation.Annotation; - -import org.springframework.core.GenericCollectionTypeResolver; -import org.springframework.core.MethodParameter; - -/** - * @author Keith Donald - * @since 3.1 - */ -class BeanPropertyDescriptor extends AbstractDescriptor { - - private final Property property; - - private final MethodParameter methodParameter; - - private final Annotation[] annotations; - - - public BeanPropertyDescriptor(Property property) { - super(property.getType()); - this.property = property; - this.methodParameter = property.getMethodParameter(); - this.annotations = property.getAnnotations(); - } - - - @Override - public Annotation[] getAnnotations() { - return this.annotations; - } - - @Override - protected Class<?> resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); - } - - @Override - protected Class<?> resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter); - } - - @Override - protected Class<?> resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter); - } - - @Override - protected AbstractDescriptor nested(Class<?> type, int typeIndex) { - MethodParameter methodParameter = new MethodParameter(this.methodParameter); - methodParameter.increaseNestingLevel(); - methodParameter.setTypeIndexForCurrentLevel(typeIndex); - return new BeanPropertyDescriptor(type, this.property, methodParameter, this.annotations); - } - - - // internal - - private BeanPropertyDescriptor(Class<?> type, Property propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) { - super(type); - this.property = propertyDescriptor; - this.methodParameter = methodParameter; - this.annotations = annotations; - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java deleted file mode 100644 index 87f8e066..00000000 --- a/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.convert; - -import java.lang.annotation.Annotation; - -import org.springframework.core.GenericCollectionTypeResolver; - -/** - * @author Keith Donald - * @author Phillip Webb - * @since 3.1 - */ -class ClassDescriptor extends AbstractDescriptor { - - ClassDescriptor(Class<?> type) { - super(type); - } - - @Override - public Annotation[] getAnnotations() { - return TypeDescriptor.EMPTY_ANNOTATION_ARRAY; - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected Class<?> resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionType((Class) getType()); - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected Class<?> resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyType((Class) getType()); - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected Class<?> resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueType((Class) getType()); - } - - @Override - protected AbstractDescriptor nested(Class<?> type, int typeIndex) { - return new ClassDescriptor(type); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java deleted file mode 100644 index 9d951bb3..00000000 --- a/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * 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 org.springframework.core.convert; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.core.GenericCollectionTypeResolver; - -/** - * @author Keith Donald - * @since 3.1 - */ -class FieldDescriptor extends AbstractDescriptor { - - private final Field field; - - private final int nestingLevel; - - private Map<Integer, Integer> typeIndexesPerLevel; - - - public FieldDescriptor(Field field) { - super(field.getType()); - this.field = field; - this.nestingLevel = 1; - } - - private FieldDescriptor(Class<?> type, Field field, int nestingLevel, int typeIndex, Map<Integer, Integer> typeIndexesPerLevel) { - super(type); - this.field = field; - this.nestingLevel = nestingLevel; - this.typeIndexesPerLevel = typeIndexesPerLevel; - this.typeIndexesPerLevel.put(nestingLevel, typeIndex); - } - - - @Override - public Annotation[] getAnnotations() { - return TypeDescriptor.nullSafeAnnotations(this.field.getAnnotations()); - } - - @Override - protected Class<?> resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel); - } - - @Override - protected Class<?> resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel); - } - - @Override - protected Class<?> resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel); - } - - @Override - protected AbstractDescriptor nested(Class<?> type, int typeIndex) { - if (this.typeIndexesPerLevel == null) { - this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4); - } - return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex, this.typeIndexesPerLevel); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java deleted file mode 100644 index c4ccebe7..00000000 --- a/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2014 the original author or authors. - * - * 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 org.springframework.core.convert; - -import java.lang.annotation.Annotation; - -import org.springframework.core.GenericCollectionTypeResolver; -import org.springframework.core.MethodParameter; - -/** - * @author Keith Donald - * @since 3.1 - */ -class ParameterDescriptor extends AbstractDescriptor { - - private final MethodParameter methodParameter; - - - public ParameterDescriptor(MethodParameter methodParameter) { - super(methodParameter.getParameterType()); - this.methodParameter = methodParameter; - } - - private ParameterDescriptor(Class<?> type, MethodParameter methodParameter) { - super(type); - this.methodParameter = methodParameter; - } - - - @Override - public Annotation[] getAnnotations() { - if (this.methodParameter.getParameterIndex() == -1) { - return TypeDescriptor.nullSafeAnnotations(this.methodParameter.getMethodAnnotations()); - } - else { - return TypeDescriptor.nullSafeAnnotations(this.methodParameter.getParameterAnnotations()); - } - } - - @Override - protected Class<?> resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); - } - - @Override - protected Class<?> resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter); - } - - @Override - protected Class<?> resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter); - } - - @Override - protected AbstractDescriptor nested(Class<?> type, int typeIndex) { - MethodParameter methodParameter = new MethodParameter(this.methodParameter); - methodParameter.increaseNestingLevel(); - methodParameter.setTypeIndexForCurrentLevel(typeIndex); - return new ParameterDescriptor(type, methodParameter); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 39d0f847..b605032d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -18,13 +18,14 @@ package org.springframework.core.convert; import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -44,37 +45,23 @@ public class TypeDescriptor implements Serializable { static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; - private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>(18); + private static final Map<Class<?>, TypeDescriptor> commonTypesCache = new HashMap<Class<?>, TypeDescriptor>(18); + + private static final Class<?>[] CACHED_COMMON_TYPES = { + boolean.class, Boolean.class, byte.class, Byte.class, char.class, Character.class, + double.class, Double.class, int.class, Integer.class, long.class, Long.class, + float.class, Float.class, short.class, Short.class, String.class, Object.class}; static { - typeDescriptorCache.put(boolean.class, new TypeDescriptor(boolean.class)); - typeDescriptorCache.put(Boolean.class, new TypeDescriptor(Boolean.class)); - typeDescriptorCache.put(byte.class, new TypeDescriptor(byte.class)); - typeDescriptorCache.put(Byte.class, new TypeDescriptor(Byte.class)); - typeDescriptorCache.put(char.class, new TypeDescriptor(char.class)); - typeDescriptorCache.put(Character.class, new TypeDescriptor(Character.class)); - typeDescriptorCache.put(double.class, new TypeDescriptor(double.class)); - typeDescriptorCache.put(Double.class, new TypeDescriptor(Double.class)); - typeDescriptorCache.put(int.class, new TypeDescriptor(int.class)); - typeDescriptorCache.put(Integer.class, new TypeDescriptor(Integer.class)); - typeDescriptorCache.put(long.class, new TypeDescriptor(long.class)); - typeDescriptorCache.put(Long.class, new TypeDescriptor(Long.class)); - typeDescriptorCache.put(float.class, new TypeDescriptor(float.class)); - typeDescriptorCache.put(Float.class, new TypeDescriptor(Float.class)); - typeDescriptorCache.put(short.class, new TypeDescriptor(short.class)); - typeDescriptorCache.put(Short.class, new TypeDescriptor(Short.class)); - typeDescriptorCache.put(String.class, new TypeDescriptor(String.class)); - typeDescriptorCache.put(Object.class, new TypeDescriptor(Object.class)); + for (Class<?> preCachedClass : CACHED_COMMON_TYPES) { + commonTypesCache.put(preCachedClass, valueOf(preCachedClass)); + } } private final Class<?> type; - private final TypeDescriptor elementTypeDescriptor; - - private final TypeDescriptor mapKeyTypeDescriptor; - - private final TypeDescriptor mapValueTypeDescriptor; + private final ResolvableType resolvableType; private final Annotation[] annotations; @@ -86,7 +73,12 @@ public class TypeDescriptor implements Serializable { * @param methodParameter the method parameter */ public TypeDescriptor(MethodParameter methodParameter) { - this(new ParameterDescriptor(methodParameter)); + Assert.notNull(methodParameter, "MethodParameter must not be null"); + this.resolvableType = ResolvableType.forMethodParameter(methodParameter); + this.type = this.resolvableType.resolve(methodParameter.getParameterType()); + this.annotations = (methodParameter.getParameterIndex() == -1 ? + nullSafeAnnotations(methodParameter.getMethodAnnotations()) : + nullSafeAnnotations(methodParameter.getParameterAnnotations())); } /** @@ -95,7 +87,10 @@ public class TypeDescriptor implements Serializable { * @param field the field */ public TypeDescriptor(Field field) { - this(new FieldDescriptor(field)); + Assert.notNull(field, "Field must not be null"); + this.resolvableType = ResolvableType.forField(field); + this.type = this.resolvableType.resolve(field.getType()); + this.annotations = nullSafeAnnotations(field.getAnnotations()); } /** @@ -105,180 +100,47 @@ public class TypeDescriptor implements Serializable { * @param property the property */ public TypeDescriptor(Property property) { - this(new BeanPropertyDescriptor(property)); + Assert.notNull(property, "Property must not be null"); + this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter()); + this.type = this.resolvableType.resolve(property.getType()); + this.annotations = nullSafeAnnotations(property.getAnnotations()); } - /** - * Create a new type descriptor from the given type. - * <p>Use this to instruct the conversion system to convert an object to a - * specific target type, when no type location such as a method parameter or - * field is available to provide additional conversion context. - * <p>Generally prefer use of {@link #forObject(Object)} for constructing type - * descriptors from source objects, as it handles the {@code null} object case. - * @param type the class - * @return the type descriptor - */ - public static TypeDescriptor valueOf(Class<?> type) { - if (type == null) { - type = Object.class; - } - TypeDescriptor desc = typeDescriptorCache.get(type); - return (desc != null ? desc : new TypeDescriptor(type)); - } - - /** - * Create a new type descriptor from a {@link java.util.Collection} type. - * <p>Useful for converting to typed Collections. - * <p>For example, a {@code List<String>} could be converted to a - * {@code List<EmailAddress>} by converting to a targetType built with this method. - * The method call to construct such a {@code TypeDescriptor} would look something - * like: {@code collection(List.class, TypeDescriptor.valueOf(EmailAddress.class));} - * @param collectionType the collection type, which must implement {@link Collection}. - * @param elementTypeDescriptor a descriptor for the collection's element type, - * used to convert collection elements - * @return the collection type descriptor + * Create a new type descriptor from a {@link ResolvableType}. This protected + * constructor is used internally and may also be used by subclasses that support + * non-Java languages with extended type systems. + * @param resolvableType the resolvable type + * @param type the backing type or {@code null} if should be resolved + * @param annotations the type annotations */ - public static TypeDescriptor collection(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) { - if (!Collection.class.isAssignableFrom(collectionType)) { - throw new IllegalArgumentException("collectionType must be a java.util.Collection"); - } - return new TypeDescriptor(collectionType, elementTypeDescriptor); + protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) { + this.resolvableType = resolvableType; + this.type = (type != null ? type : resolvableType.resolve(Object.class)); + this.annotations = nullSafeAnnotations(annotations); } - /** - * Create a new type descriptor from a {@link java.util.Map} type. - * <p>Useful for converting to typed Maps. - * <p>For example, a Map<String, String> could be converted to a Map<Id, EmailAddress> - * by converting to a targetType built with this method: - * The method call to construct such a TypeDescriptor would look something like: - * map(Map.class, TypeDescriptor.valueOf(Id.class), TypeDescriptor.valueOf(EmailAddress.class)); - * @param mapType the map type, which must implement {@link Map} - * @param keyTypeDescriptor a descriptor for the map's key type, used to convert map keys - * @param valueTypeDescriptor the map's value type, used to convert map values - * @return the map type descriptor - */ - public static TypeDescriptor map(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) { - if (!Map.class.isAssignableFrom(mapType)) { - throw new IllegalArgumentException("mapType must be a java.util.Map"); - } - return new TypeDescriptor(mapType, keyTypeDescriptor, valueTypeDescriptor); - } - - /** - * Create a new type descriptor as an array of the specified type. - * <p>For example to create a {@code Map<String,String>[]} use - * {@code TypeDescriptor.array(TypeDescriptor.map(Map.class, TypeDescriptor.value(String.class), TypeDescriptor.value(String.class)))}. - * @param elementTypeDescriptor the {@link TypeDescriptor} of the array element or {@code null} - * @return an array {@link TypeDescriptor} or {@code null} if {@code elementTypeDescriptor} is {@code null} - * @since 3.2.1 - */ - public static TypeDescriptor array(TypeDescriptor elementTypeDescriptor) { - if (elementTypeDescriptor == null) { - return null; - } - Class<?> type = Array.newInstance(elementTypeDescriptor.getType(), 0).getClass(); - return new TypeDescriptor(type, elementTypeDescriptor, null, null, elementTypeDescriptor.getAnnotations()); - } - - /** - * Creates a type descriptor for a nested type declared within the method parameter. - * <p>For example, if the methodParameter is a {@code List<String>} and the - * nesting level is 1, the nested type descriptor will be String.class. - * <p>If the methodParameter is a {@code List<List<String>>} and the nesting - * level is 2, the nested type descriptor will also be a String.class. - * <p>If the methodParameter is a {@code Map<Integer, String>} and the nesting - * level is 1, the nested type descriptor will be String, derived from the map value. - * <p>If the methodParameter is a {@code List<Map<Integer, String>>} and the - * nesting level is 2, the nested type descriptor will be String, derived from the map value. - * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. - * For example, if the method parameter is a {@code List<?>}, the nested type - * descriptor returned will be {@code null}. - * @param methodParameter the method parameter with a nestingLevel of 1 - * @param nestingLevel the nesting level of the collection/array element or - * map key/value declaration within the method parameter - * @return the nested type descriptor at the specified nesting level, or null - * if it could not be obtained - * @throws IllegalArgumentException if the nesting level of the input - * {@link MethodParameter} argument is not 1, or if the types up to the - * specified nesting level are not of collection, array, or map types - */ - public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) { - if (methodParameter.getNestingLevel() != 1) { - throw new IllegalArgumentException("methodParameter nesting level must be 1: " + - "use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal"); - } - return nested(new ParameterDescriptor(methodParameter), nestingLevel); - } - - /** - * Creates a type descriptor for a nested type declared within the field. - * <p>For example, if the field is a {@code List<String>} and the nesting - * level is 1, the nested type descriptor will be {@code String.class}. - * <p>If the field is a {@code List<List<String>>} and the nesting level is - * 2, the nested type descriptor will also be a {@code String.class}. - * <p>If the field is a {@code Map<Integer, String>} and the nesting level - * is 1, the nested type descriptor will be String, derived from the map value. - * <p>If the field is a {@code List<Map<Integer, String>>} and the nesting - * level is 2, the nested type descriptor will be String, derived from the map value. - * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. - * For example, if the field is a {@code List<?>}, the nested type descriptor returned will be {@code null}. - * @param field the field - * @param nestingLevel the nesting level of the collection/array element or - * map key/value declaration within the field - * @return the nested type descriptor at the specified nesting level, or null - * if it could not be obtained - * @throws IllegalArgumentException if the types up to the specified nesting - * level are not of collection, array, or map types - */ - public static TypeDescriptor nested(Field field, int nestingLevel) { - return nested(new FieldDescriptor(field), nestingLevel); - } - /** - * Creates a type descriptor for a nested type declared within the property. - * <p>For example, if the property is a {@code List<String>} and the nesting - * level is 1, the nested type descriptor will be {@code String.class}. - * <p>If the property is a {@code List<List<String>>} and the nesting level - * is 2, the nested type descriptor will also be a {@code String.class}. - * <p>If the property is a {@code Map<Integer, String>} and the nesting level - * is 1, the nested type descriptor will be String, derived from the map value. - * <p>If the property is a {@code List<Map<Integer, String>>} and the nesting - * level is 2, the nested type descriptor will be String, derived from the map value. - * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. - * For example, if the property is a {@code List<?>}, the nested type descriptor - * returned will be {@code null}. - * @param property the property - * @param nestingLevel the nesting level of the collection/array element or - * map key/value declaration within the property - * @return the nested type descriptor at the specified nesting level, or - * {@code null} if it could not be obtained - * @throws IllegalArgumentException if the types up to the specified nesting - * level are not of collection, array, or map types - */ - public static TypeDescriptor nested(Property property, int nestingLevel) { - return nested(new BeanPropertyDescriptor(property), nestingLevel); + private Annotation[] nullSafeAnnotations(Annotation[] annotations) { + return (annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY); } /** - * Create a new type descriptor for an object. - * <p>Use this factory method to introspect a source object before asking the - * conversion system to convert it to some another type. - * <p>If the provided object is null, returns null, else calls {@link #valueOf(Class)} - * to build a TypeDescriptor from the object's class. - * @param source the source object - * @return the type descriptor + * Variation of {@link #getType()} that accounts for a primitive type by + * returning its object wrapper type. + * <p>This is useful for conversion service implementations that wish to + * normalize to object-based types and not work with primitive types directly. */ - public static TypeDescriptor forObject(Object source) { - return (source != null ? valueOf(source.getClass()) : null); + public Class<?> getObjectType() { + return ClassUtils.resolvePrimitiveIfNecessary(getType()); } - /** - * The type of the backing class, method parameter, field, or property described by this TypeDescriptor. + * The type of the backing class, method parameter, field, or property + * described by this TypeDescriptor. * <p>Returns primitive types as-is. - * <p>See {@link #getObjectType()} for a variation of this operation that resolves primitive types - * to their corresponding Object types if necessary. + * <p>See {@link #getObjectType()} for a variation of this operation that + * resolves primitive types to their corresponding Object types if necessary. * @return the type, or {@code null} * @see #getObjectType() */ @@ -287,32 +149,46 @@ public class TypeDescriptor implements Serializable { } /** - * Variation of {@link #getType()} that accounts for a primitive type by returning its object wrapper type. - * <p>This is useful for conversion service implementations that wish to normalize to object-based types - * and not work with primitive types directly. + * Return the underlying {@link ResolvableType}. + * @since 4.0 */ - public Class<?> getObjectType() { - return ClassUtils.resolvePrimitiveIfNecessary(getType()); + public ResolvableType getResolvableType() { + return this.resolvableType; + } + + /** + * Return the underlying source of the descriptor. Will return a {@link Field}, + * {@link MethodParameter} or {@link Type} depending on how the {@link TypeDescriptor} + * was constructed. This method is primarily to provide access to additional + * type information or meta-data that alternative JVM languages may provide. + * @since 4.0 + */ + public Object getSource() { + return (this.resolvableType != null ? this.resolvableType.getSource() : null); } /** - * Narrows this {@link TypeDescriptor} by setting its type to the class of the provided value. - * <p>If the value is {@code null}, no narrowing is performed and this TypeDescriptor is returned unchanged. - * <p>Designed to be called by binding frameworks when they read property, field, or method return values. - * Allows such frameworks to narrow a TypeDescriptor built from a declared property, field, or method return - * value type. For example, a field declared as {@code java.lang.Object} would be narrowed to - * {@code java.util.HashMap} if it was set to a {@code java.util.HashMap} value. The narrowed - * TypeDescriptor can then be used to convert the HashMap to some other type. Annotation and - * nested type context is preserved by the narrowed copy. + * Narrows this {@link TypeDescriptor} by setting its type to the class of the + * provided value. + * <p>If the value is {@code null}, no narrowing is performed and this TypeDescriptor + * is returned unchanged. + * <p>Designed to be called by binding frameworks when they read property, field, + * or method return values. Allows such frameworks to narrow a TypeDescriptor built + * from a declared property, field, or method return value type. For example, a field + * declared as {@code java.lang.Object} would be narrowed to {@code java.util.HashMap} + * if it was set to a {@code java.util.HashMap} value. The narrowed TypeDescriptor + * can then be used to convert the HashMap to some other type. Annotation and nested + * type context is preserved by the narrowed copy. * @param value the value to use for narrowing this type descriptor - * @return this TypeDescriptor narrowed (returns a copy with its type updated to the class of the provided value) + * @return this TypeDescriptor narrowed (returns a copy with its type updated to the + * class of the provided value) */ public TypeDescriptor narrow(Object value) { if (value == null) { return this; } - return new TypeDescriptor(value.getClass(), this.elementTypeDescriptor, - this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations); + ResolvableType narrowed = ResolvableType.forType(value.getClass(), this.resolvableType); + return new TypeDescriptor(narrowed, null, this.annotations); } /** @@ -328,8 +204,7 @@ public class TypeDescriptor implements Serializable { return null; } Assert.isAssignable(superType, getType()); - return new TypeDescriptor(superType, this.elementTypeDescriptor, - this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations); + return new TypeDescriptor(this.resolvableType.as(superType), superType, this.annotations); } /** @@ -360,7 +235,7 @@ public class TypeDescriptor implements Serializable { * @return <tt>true</tt> if the annotation is present */ public boolean hasAnnotation(Class<? extends Annotation> annotationType) { - return getAnnotation(annotationType) != null; + return (getAnnotation(annotationType) != null); } /** @@ -370,12 +245,12 @@ public class TypeDescriptor implements Serializable { */ @SuppressWarnings("unchecked") public <T extends Annotation> T getAnnotation(Class<T> annotationType) { - for (Annotation annotation : this.annotations) { + for (Annotation annotation : getAnnotations()) { if (annotation.annotationType().equals(annotationType)) { return (T) annotation; } } - for (Annotation metaAnn : this.annotations) { + for (Annotation metaAnn : getAnnotations()) { T ann = metaAnn.annotationType().getAnnotation(annotationType); if (ann != null) { return ann; @@ -385,16 +260,17 @@ public class TypeDescriptor implements Serializable { } /** - * Returns true if an object of this type descriptor can be assigned to the location described by the - * given type descriptor. - * <p>For example, valueOf(String.class).isAssignableTo(valueOf(CharSequence.class)) returns true - * because a String value can be assigned to a CharSequence variable. On the other hand, - * valueOf(Number.class).isAssignableTo(valueOf(Integer.class)) returns false because, - * while all Integers are Numbers, not all Numbers are Integers. + * Returns true if an object of this type descriptor can be assigned to the location + * described by the given type descriptor. + * <p>For example, {@code valueOf(String.class).isAssignableTo(valueOf(CharSequence.class))} + * returns {@code true} because a String value can be assigned to a CharSequence variable. + * On the other hand, {@code valueOf(Number.class).isAssignableTo(valueOf(Integer.class))} + * returns {@code false} because, while all Integers are Numbers, not all Numbers are Integers. * <p>For arrays, collections, and maps, element and key/value types are checked if declared. * For example, a List<String> field value is assignable to a Collection<CharSequence> * field, but List<Number> is not assignable to List<Integer>. - * @return true if this type is assignable to the type represented by the provided type descriptor + * @return {@code true} if this type is assignable to the type represented by the provided + * type descriptor * @see #getObjectType() */ public boolean isAssignableTo(TypeDescriptor typeDescriptor) { @@ -417,8 +293,12 @@ public class TypeDescriptor implements Serializable { } } - - // indexable type descriptor operations + private boolean isNestedAssignable(TypeDescriptor nestedTypeDescriptor, TypeDescriptor otherNestedTypeDescriptor) { + if (nestedTypeDescriptor == null || otherNestedTypeDescriptor == null) { + return true; + } + return nestedTypeDescriptor.isAssignableTo(otherNestedTypeDescriptor); + } /** * Is this type a {@link Collection} type? @@ -437,37 +317,40 @@ public class TypeDescriptor implements Serializable { /** * If this type is an array, returns the array's component type. * If this type is a {@link Collection} and it is parameterized, returns the Collection's element type. - * If the Collection is not parameterized, returns null indicating the element type is not declared. + * If the Collection is not parameterized, returns {@code null} indicating the element type is not declared. * @return the array component type or Collection element type, or {@code null} if this type is a * Collection but its element type is not parameterized - * @throws IllegalStateException if this type is not a java.util.Collection or Array type + * @throws IllegalStateException if this type is not a {@code java.util.Collection} or array type */ public TypeDescriptor getElementTypeDescriptor() { - assertCollectionOrArray(); - return this.elementTypeDescriptor; + if (this.resolvableType.isArray()) { + return new TypeDescriptor(this.resolvableType.getComponentType(), null, this.annotations); + } + return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric()); } /** - * If this type is a {@link Collection} or an Array, creates a element TypeDescriptor from the provided - * collection or array element. - * <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class of the provided - * collection or array element. For example, if this describes a java.util.List<java.lang.Number< - * and the element argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer. - * If this describes a java.util.List<?> and the element argument is a java.lang.Integer, the returned - * TypeDescriptor will be java.lang.Integer as well. - * <p>Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. + * If this type is a {@link Collection} or an array, creates a element TypeDescriptor + * from the provided collection or array element. + * <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class + * of the provided collection or array element. For example, if this describes a + * {@code java.util.List<java.lang.Number<} and the element argument is an + * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}. + * If this describes a {@code java.util.List<?>} and the element argument is an + * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer} + * as well. + * <p>Annotation and nested type context will be preserved in the narrowed + * TypeDescriptor that is returned. * @param element the collection or array element * @return a element type descriptor, narrowed to the type of the provided element - * @throws IllegalStateException if this type is not a java.util.Collection or Array type + * @throws IllegalStateException if this type is not a {@code java.util.Collection} + * or array type * @see #narrow(Object) */ public TypeDescriptor elementTypeDescriptor(Object element) { return narrow(element, getElementTypeDescriptor()); } - - // map type descriptor operations - /** * Is this type a {@link Map} type? */ @@ -476,27 +359,33 @@ public class TypeDescriptor implements Serializable { } /** - * If this type is a {@link Map} and its key type is parameterized, returns the map's key type. - * If the Map's key type is not parameterized, returns null indicating the key type is not declared. - * @return the Map key type, or {@code null} if this type is a Map but its key type is not parameterized - * @throws IllegalStateException if this type is not a java.util.Map + * If this type is a {@link Map} and its key type is parameterized, + * returns the map's key type. If the Map's key type is not parameterized, + * returns {@code null} indicating the key type is not declared. + * @return the Map key type, or {@code null} if this type is a Map + * but its key type is not parameterized + * @throws IllegalStateException if this type is not a {@code java.util.Map} */ public TypeDescriptor getMapKeyTypeDescriptor() { - assertMap(); - return this.mapKeyTypeDescriptor; - } - - /** - * If this type is a {@link Map}, creates a mapKey {@link TypeDescriptor} from the provided map key. - * <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property to the class of the provided map key. - * For example, if this describes a java.util.Map<java.lang.Number, java.lang.String< and the key argument - * is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer. - * <p>If this describes a java.util.Map<?, ?> and the key argument is a java.lang.Integer, the returned - * TypeDescriptor will be java.lang.Integer as well. - * <p>Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. + Assert.state(isMap(), "Not a java.util.Map"); + return getRelatedIfResolvable(this, this.resolvableType.asMap().getGeneric(0)); + } + + /** + * If this type is a {@link Map}, creates a mapKey {@link TypeDescriptor} + * from the provided map key. + * <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property + * to the class of the provided map key. For example, if this describes a + * {@code java.util.Map<java.lang.Number, java.lang.String<} and the key + * argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be + * {@code java.lang.Integer}. If this describes a {@code java.util.Map<?, ?>} + * and the key argument is a {@code java.lang.Integer}, the returned + * TypeDescriptor will be {@code java.lang.Integer} as well. + * <p>Annotation and nested type context will be preserved in the narrowed + * TypeDescriptor that is returned. * @param mapKey the map key * @return the map key type descriptor - * @throws IllegalStateException if this type is not a java.util.Map + * @throws IllegalStateException if this type is not a {@code java.util.Map} * @see #narrow(Object) */ public TypeDescriptor getMapKeyTypeDescriptor(Object mapKey) { @@ -504,151 +393,85 @@ public class TypeDescriptor implements Serializable { } /** - * If this type is a {@link Map} and its value type is parameterized, returns the map's value type. - * <p>If the Map's value type is not parameterized, returns null indicating the value type is not declared. - * @return the Map value type, or {@code null} if this type is a Map but its value type is not parameterized - * @throws IllegalStateException if this type is not a java.util.Map + * If this type is a {@link Map} and its value type is parameterized, + * returns the map's value type. + * <p>If the Map's value type is not parameterized, returns {@code null} + * indicating the value type is not declared. + * @return the Map value type, or {@code null} if this type is a Map + * but its value type is not parameterized + * @throws IllegalStateException if this type is not a {@code java.util.Map} */ public TypeDescriptor getMapValueTypeDescriptor() { - assertMap(); - return this.mapValueTypeDescriptor; - } - - /** - * If this type is a {@link Map}, creates a mapValue {@link TypeDescriptor} from the provided map value. - * <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property to the class of the provided - * map value. For example, if this describes a java.util.Map<java.lang.String, java.lang.Number< - * and the value argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer. - * If this describes a java.util.Map<?, ?> and the value argument is a java.lang.Integer, the - * returned TypeDescriptor will be java.lang.Integer as well. - * <p>Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. + Assert.state(isMap(), "Not a java.util.Map"); + return getRelatedIfResolvable(this, this.resolvableType.asMap().getGeneric(1)); + } + + /** + * If this type is a {@link Map}, creates a mapValue {@link TypeDescriptor} + * from the provided map value. + * <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property + * to the class of the provided map value. For example, if this describes a + * {@code java.util.Map<java.lang.String, java.lang.Number<} and the value + * argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be + * {@code java.lang.Integer}. If this describes a {@code java.util.Map<?, ?>} + * and the value argument is a {@code java.lang.Integer}, the returned + * TypeDescriptor will be {@code java.lang.Integer} as well. + * <p>Annotation and nested type context will be preserved in the narrowed + * TypeDescriptor that is returned. * @param mapValue the map value * @return the map value type descriptor - * @throws IllegalStateException if this type is not a java.util.Map + * @throws IllegalStateException if this type is not a {@code java.util.Map} + * @see #narrow(Object) */ public TypeDescriptor getMapValueTypeDescriptor(Object mapValue) { return narrow(mapValue, getMapValueTypeDescriptor()); } - - // deprecations in Spring 3.1 - /** * Returns the value of {@link TypeDescriptor#getType() getType()} for the * {@link #getElementTypeDescriptor() elementTypeDescriptor}. * @deprecated in Spring 3.1 in favor of {@link #getElementTypeDescriptor()} - * @throws IllegalStateException if this type is not a java.util.Collection or Array type + * @throws IllegalStateException if this type is not a {@code java.util.Collection} or array type */ @Deprecated public Class<?> getElementType() { - return getElementTypeDescriptor().getType(); + return getType(getElementTypeDescriptor()); } /** * Returns the value of {@link TypeDescriptor#getType() getType()} for the * {@link #getMapKeyTypeDescriptor() getMapKeyTypeDescriptor}. * @deprecated in Spring 3.1 in favor of {@link #getMapKeyTypeDescriptor()} - * @throws IllegalStateException if this type is not a java.util.Map + * @throws IllegalStateException if this type is not a {@code java.util.Map} */ @Deprecated public Class<?> getMapKeyType() { - return getMapKeyTypeDescriptor().getType(); + return getType(getMapKeyTypeDescriptor()); } /** * Returns the value of {@link TypeDescriptor#getType() getType()} for the * {@link #getMapValueTypeDescriptor() getMapValueTypeDescriptor}. * @deprecated in Spring 3.1 in favor of {@link #getMapValueTypeDescriptor()} - * @throws IllegalStateException if this type is not a java.util.Map + * @throws IllegalStateException if this type is not a {@code java.util.Map} */ @Deprecated public Class<?> getMapValueType() { - return getMapValueTypeDescriptor().getType(); - } - - - // internal constructors - - private TypeDescriptor(Class<?> type) { - this(new ClassDescriptor(type)); - } - - private TypeDescriptor(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) { - this(collectionType, elementTypeDescriptor, null, null, EMPTY_ANNOTATION_ARRAY); + return getType(getMapValueTypeDescriptor()); } - private TypeDescriptor(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) { - this(mapType, null, keyTypeDescriptor, valueTypeDescriptor, EMPTY_ANNOTATION_ARRAY); - } - - private TypeDescriptor(Class<?> type, TypeDescriptor elementTypeDescriptor, TypeDescriptor mapKeyTypeDescriptor, - TypeDescriptor mapValueTypeDescriptor, Annotation[] annotations) { - - this.type = type; - this.elementTypeDescriptor = elementTypeDescriptor; - this.mapKeyTypeDescriptor = mapKeyTypeDescriptor; - this.mapValueTypeDescriptor = mapValueTypeDescriptor; - this.annotations = annotations; - } - - TypeDescriptor(AbstractDescriptor descriptor) { - this.type = descriptor.getType(); - this.elementTypeDescriptor = descriptor.getElementTypeDescriptor(); - this.mapKeyTypeDescriptor = descriptor.getMapKeyTypeDescriptor(); - this.mapValueTypeDescriptor = descriptor.getMapValueTypeDescriptor(); - this.annotations = descriptor.getAnnotations(); - } - - - // internal helpers - - static Annotation[] nullSafeAnnotations(Annotation[] annotations) { - return (annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY); - } - - private static TypeDescriptor nested(AbstractDescriptor descriptor, int nestingLevel) { - for (int i = 0; i < nestingLevel; i++) { - descriptor = descriptor.nested(); - if (descriptor == null) { - return null; - } - } - return new TypeDescriptor(descriptor); - } - - private void assertCollectionOrArray() { - if (!isCollection() && !isArray()) { - throw new IllegalStateException("Not a java.util.Collection or Array"); - } - } - - private void assertMap() { - if (!isMap()) { - throw new IllegalStateException("Not a java.util.Map"); - } + private Class<?> getType(TypeDescriptor typeDescriptor) { + return (typeDescriptor != null ? typeDescriptor.getType() : null); } private TypeDescriptor narrow(Object value, TypeDescriptor typeDescriptor) { if (typeDescriptor != null) { return typeDescriptor.narrow(value); } - else { - return (value != null ? new TypeDescriptor(value.getClass(), null, null, null, this.annotations) : null); - } - } - - private boolean isNestedAssignable(TypeDescriptor nestedTypeDescriptor, TypeDescriptor otherNestedTypeDescriptor) { - if (nestedTypeDescriptor == null || otherNestedTypeDescriptor == null) { - return true; - } - return nestedTypeDescriptor.isAssignableTo(otherNestedTypeDescriptor); - } - - private String wildcard(TypeDescriptor typeDescriptor) { - return (typeDescriptor != null ? typeDescriptor.toString() : "?"); + return (value != null ? new TypeDescriptor(this.resolvableType, value.getClass(), this.annotations) : null); } - + @Override public boolean equals(Object obj) { if (this == obj) { return true; @@ -660,44 +483,235 @@ public class TypeDescriptor implements Serializable { if (!ObjectUtils.nullSafeEquals(this.type, other.type)) { return false; } - if (this.annotations.length != other.annotations.length) { + if (getAnnotations().length != other.getAnnotations().length) { return false; } - for (Annotation ann : this.annotations) { + for (Annotation ann : getAnnotations()) { if (other.getAnnotation(ann.annotationType()) == null) { return false; } } if (isCollection() || isArray()) { - return ObjectUtils.nullSafeEquals(this.elementTypeDescriptor, other.elementTypeDescriptor); + return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor()); } else if (isMap()) { - return ObjectUtils.nullSafeEquals(this.mapKeyTypeDescriptor, other.mapKeyTypeDescriptor) && - ObjectUtils.nullSafeEquals(this.mapValueTypeDescriptor, other.mapValueTypeDescriptor); + return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) && + ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor()); } else { return true; } } + @Override public int hashCode() { return getType().hashCode(); } + @Override public String toString() { StringBuilder builder = new StringBuilder(); - for (Annotation ann : this.annotations) { + for (Annotation ann : getAnnotations()) { builder.append("@").append(ann.annotationType().getName()).append(' '); } - builder.append(ClassUtils.getQualifiedName(getType())); - if (isMap()) { - builder.append("<").append(wildcard(this.mapKeyTypeDescriptor)); - builder.append(", ").append(wildcard(this.mapValueTypeDescriptor)).append(">"); + builder.append(this.resolvableType.toString()); + return builder.toString(); + } + + /** + * Create a new type descriptor from the given type. + * <p>Use this to instruct the conversion system to convert an object to a + * specific target type, when no type location such as a method parameter or + * field is available to provide additional conversion context. + * <p>Generally prefer use of {@link #forObject(Object)} for constructing type + * descriptors from source objects, as it handles the {@code null} object case. + * @param type the class (may be {@code null} to indicate {@code Object.class}) + * @return the corresponding type descriptor + */ + public static TypeDescriptor valueOf(Class<?> type) { + if (type == null) { + type = Object.class; } - else if (isCollection()) { - builder.append("<").append(wildcard(this.elementTypeDescriptor)).append(">"); + TypeDescriptor desc = commonTypesCache.get(type); + return (desc != null ? desc : new TypeDescriptor(ResolvableType.forClass(type), null, null)); + } + + /** + * Create a new type descriptor from a {@link java.util.Collection} type. + * <p>Useful for converting to typed Collections. + * <p>For example, a {@code List<String>} could be converted to a + * {@code List<EmailAddress>} by converting to a targetType built with this method. + * The method call to construct such a {@code TypeDescriptor} would look something + * like: {@code collection(List.class, TypeDescriptor.valueOf(EmailAddress.class));} + * @param collectionType the collection type, which must implement {@link Collection}. + * @param elementTypeDescriptor a descriptor for the collection's element type, + * used to convert collection elements + * @return the collection type descriptor + */ + public static TypeDescriptor collection(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) { + Assert.notNull(collectionType, "collectionType must not be null"); + if (!Collection.class.isAssignableFrom(collectionType)) { + throw new IllegalArgumentException("collectionType must be a java.util.Collection"); } - return builder.toString(); + ResolvableType element = (elementTypeDescriptor != null ? elementTypeDescriptor.resolvableType : null); + return new TypeDescriptor(ResolvableType.forClassWithGenerics(collectionType, element), null, null); + } + + /** + * Create a new type descriptor from a {@link java.util.Map} type. + * <p>Useful for converting to typed Maps. + * <p>For example, a Map<String, String> could be converted to a Map<Id, EmailAddress> + * by converting to a targetType built with this method: + * The method call to construct such a TypeDescriptor would look something like: + * <pre class="code"> + * map(Map.class, TypeDescriptor.valueOf(Id.class), TypeDescriptor.valueOf(EmailAddress.class)); + * </pre> + * @param mapType the map type, which must implement {@link Map} + * @param keyTypeDescriptor a descriptor for the map's key type, used to convert map keys + * @param valueTypeDescriptor the map's value type, used to convert map values + * @return the map type descriptor + */ + public static TypeDescriptor map(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) { + if (!Map.class.isAssignableFrom(mapType)) { + throw new IllegalArgumentException("mapType must be a java.util.Map"); + } + ResolvableType key = (keyTypeDescriptor != null ? keyTypeDescriptor.resolvableType : null); + ResolvableType value = (valueTypeDescriptor != null ? valueTypeDescriptor.resolvableType : null); + return new TypeDescriptor(ResolvableType.forClassWithGenerics(mapType, key, value), null, null); + } + + /** + * Create a new type descriptor as an array of the specified type. + * <p>For example to create a {@code Map<String,String>[]} use: + * <pre class="code"> + * TypeDescriptor.array(TypeDescriptor.map(Map.class, TypeDescriptor.value(String.class), TypeDescriptor.value(String.class))); + * </pre> + * @param elementTypeDescriptor the {@link TypeDescriptor} of the array element or {@code null} + * @return an array {@link TypeDescriptor} or {@code null} if {@code elementTypeDescriptor} is {@code null} + * @since 3.2.1 + */ + public static TypeDescriptor array(TypeDescriptor elementTypeDescriptor) { + if (elementTypeDescriptor == null) { + return null; + } + return new TypeDescriptor(ResolvableType.forArrayComponent(elementTypeDescriptor.resolvableType), + null, elementTypeDescriptor.getAnnotations()); + } + + /** + * Creates a type descriptor for a nested type declared within the method parameter. + * <p>For example, if the methodParameter is a {@code List<String>} and the + * nesting level is 1, the nested type descriptor will be String.class. + * <p>If the methodParameter is a {@code List<List<String>>} and the nesting + * level is 2, the nested type descriptor will also be a String.class. + * <p>If the methodParameter is a {@code Map<Integer, String>} and the nesting + * level is 1, the nested type descriptor will be String, derived from the map value. + * <p>If the methodParameter is a {@code List<Map<Integer, String>>} and the + * nesting level is 2, the nested type descriptor will be String, derived from the map value. + * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. + * For example, if the method parameter is a {@code List<?>}, the nested type + * descriptor returned will be {@code null}. + * @param methodParameter the method parameter with a nestingLevel of 1 + * @param nestingLevel the nesting level of the collection/array element or + * map key/value declaration within the method parameter + * @return the nested type descriptor at the specified nesting level, + * or {@code null} if it could not be obtained + * @throws IllegalArgumentException if the nesting level of the input + * {@link MethodParameter} argument is not 1, or if the types up to the + * specified nesting level are not of collection, array, or map types + */ + public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) { + if (methodParameter.getNestingLevel() != 1) { + throw new IllegalArgumentException("MethodParameter nesting level must be 1: " + + "use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal"); + } + return nested(new TypeDescriptor(methodParameter), nestingLevel); + } + + /** + * Creates a type descriptor for a nested type declared within the field. + * <p>For example, if the field is a {@code List<String>} and the nesting + * level is 1, the nested type descriptor will be {@code String.class}. + * <p>If the field is a {@code List<List<String>>} and the nesting level is + * 2, the nested type descriptor will also be a {@code String.class}. + * <p>If the field is a {@code Map<Integer, String>} and the nesting level + * is 1, the nested type descriptor will be String, derived from the map value. + * <p>If the field is a {@code List<Map<Integer, String>>} and the nesting + * level is 2, the nested type descriptor will be String, derived from the map value. + * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. + * For example, if the field is a {@code List<?>}, the nested type descriptor returned will be {@code null}. + * @param field the field + * @param nestingLevel the nesting level of the collection/array element or + * map key/value declaration within the field + * @return the nested type descriptor at the specified nesting level, + * or {@code null} if it could not be obtained + * @throws IllegalArgumentException if the types up to the specified nesting + * level are not of collection, array, or map types + */ + public static TypeDescriptor nested(Field field, int nestingLevel) { + return nested(new TypeDescriptor(field), nestingLevel); + } + + /** + * Creates a type descriptor for a nested type declared within the property. + * <p>For example, if the property is a {@code List<String>} and the nesting + * level is 1, the nested type descriptor will be {@code String.class}. + * <p>If the property is a {@code List<List<String>>} and the nesting level + * is 2, the nested type descriptor will also be a {@code String.class}. + * <p>If the property is a {@code Map<Integer, String>} and the nesting level + * is 1, the nested type descriptor will be String, derived from the map value. + * <p>If the property is a {@code List<Map<Integer, String>>} and the nesting + * level is 2, the nested type descriptor will be String, derived from the map value. + * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared. + * For example, if the property is a {@code List<?>}, the nested type descriptor + * returned will be {@code null}. + * @param property the property + * @param nestingLevel the nesting level of the collection/array element or + * map key/value declaration within the property + * @return the nested type descriptor at the specified nesting level, or + * {@code null} if it could not be obtained + * @throws IllegalArgumentException if the types up to the specified nesting + * level are not of collection, array, or map types + */ + public static TypeDescriptor nested(Property property, int nestingLevel) { + return nested(new TypeDescriptor(property), nestingLevel); + } + + /** + * Create a new type descriptor for an object. + * <p>Use this factory method to introspect a source object before asking the + * conversion system to convert it to some another type. + * <p>If the provided object is {@code null}, returns {@code null}, else calls + * {@link #valueOf(Class)} to build a TypeDescriptor from the object's class. + * @param source the source object + * @return the type descriptor + */ + public static TypeDescriptor forObject(Object source) { + return (source != null ? valueOf(source.getClass()) : null); + } + + private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) { + ResolvableType nested = typeDescriptor.resolvableType; + for (int i = 0; i < nestingLevel; i++) { + if (Object.class.equals(nested.getType())) { + // Could be a collection type but we don't know about its element type, + // so let's just assume there is an element type of type Object... + } + else { + nested = nested.getNested(2); + } + } + if (nested == ResolvableType.NONE) { + return null; + } + return getRelatedIfResolvable(typeDescriptor, nested); + } + + private static TypeDescriptor getRelatedIfResolvable(TypeDescriptor source, ResolvableType type) { + if (type.resolve() == null) { + return null; + } + return new TypeDescriptor(type, null, source.annotations); } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java index 3e2d7415..31c51128 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java +++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java @@ -77,6 +77,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> { } + @Override public int compare(S o1, S o2) { T c1 = this.converter.convert(o1); T c2 = this.converter.convert(o2); @@ -94,6 +95,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> { Comparator<K> comparator) { return new ConvertingComparator<Map.Entry<K,V>, K>(comparator, new Converter<Map.Entry<K, V>, K>() { + @Override public K convert(Map.Entry<K, V> source) { return source.getKey(); } @@ -111,6 +113,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> { Comparator<V> comparator) { return new ConvertingComparator<Map.Entry<K,V>, V>(comparator, new Converter<Map.Entry<K, V>, V>() { + @Override public V convert(Map.Entry<K, V> source) { return source.getValue(); } @@ -135,6 +138,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> { this.targetType = targetType; } + @Override public T convert(S source) { return this.conversionService.convert(source, this.targetType); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java index bb5c7331..df3b730d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java @@ -48,14 +48,17 @@ final class ArrayToArrayConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object[].class, Object[].class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return this.helperConverter.matches(sourceType, targetType); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (this.conversionService instanceof GenericConversionService && ((GenericConversionService) this.conversionService).canBypassConvert( diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java index d034eddb..50c57870 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java @@ -48,16 +48,18 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object[].class, Collection.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements( sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService); } - @SuppressWarnings("unchecked") + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java index f100458a..ef92f0bd 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java @@ -41,14 +41,17 @@ final class ArrayToObjectConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object[].class, Object.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java index 1307b991..80f986a6 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java @@ -43,14 +43,17 @@ final class ArrayToStringConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object[].class, String.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return this.helperConverter.matches(sourceType, targetType); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ByteBufferConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ByteBufferConverter.java new file mode 100644 index 00000000..ec578fd6 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ByteBufferConverter.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core.convert.support; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalGenericConverter; + +/** + * Converts a {@link ByteBuffer} directly to and from {@code byte[]}s and indirectly to + * any type that the {@link ConversionService} support via {@code byte[]}. + * + * @author Phillip Webb + * @since 4.0 + */ +final class ByteBufferConverter implements ConditionalGenericConverter { + + private static final TypeDescriptor BYTE_BUFFER_TYPE = TypeDescriptor.valueOf(ByteBuffer.class); + + private static final TypeDescriptor BYTE_ARRAY_TYPE = TypeDescriptor.valueOf(byte[].class); + + private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS; + static { + Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>(); + convertiblePairs.add(new ConvertiblePair(ByteBuffer.class, Object.class)); + convertiblePairs.add(new ConvertiblePair(Object.class, ByteBuffer.class)); + CONVERTIBLE_PAIRS = Collections.unmodifiableSet(convertiblePairs); + } + + + private ConversionService conversionService; + + + public ByteBufferConverter(ConversionService conversionService) { + this.conversionService = conversionService; + } + + + @Override + public Set<ConvertiblePair> getConvertibleTypes() { + return CONVERTIBLE_PAIRS; + } + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.isAssignableTo(BYTE_BUFFER_TYPE)) { + return matchesFromByteBuffer(targetType); + } + if (targetType.isAssignableTo(BYTE_BUFFER_TYPE)) { + return matchesToByteBuffer(sourceType); + } + return false; + } + + private boolean matchesFromByteBuffer(TypeDescriptor targetType) { + return (targetType.isAssignableTo(BYTE_ARRAY_TYPE) || this.conversionService.canConvert( + BYTE_ARRAY_TYPE, targetType)); + } + + private boolean matchesToByteBuffer(TypeDescriptor sourceType) { + return (sourceType.isAssignableTo(BYTE_ARRAY_TYPE) || this.conversionService.canConvert( + sourceType, BYTE_ARRAY_TYPE)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, + TypeDescriptor targetType) { + if (sourceType.isAssignableTo(BYTE_BUFFER_TYPE)) { + return convertFromByteBuffer((ByteBuffer) source, targetType); + } + if (targetType.isAssignableTo(BYTE_BUFFER_TYPE)) { + return convertToByteBuffer(source, sourceType); + } + // Should not happen + throw new IllegalStateException("Unexpected source/target types"); + } + + private Object convertFromByteBuffer(ByteBuffer source, TypeDescriptor targetType) { + byte[] bytes = new byte[source.remaining()]; + source.get(bytes); + if (targetType.isAssignableTo(BYTE_ARRAY_TYPE)) { + return bytes; + } + return this.conversionService.convert(bytes, BYTE_ARRAY_TYPE, targetType); + } + + private Object convertToByteBuffer(Object source, TypeDescriptor sourceType) { + byte[] bytes = (byte[]) (source instanceof byte[] ? source + : this.conversionService.convert(source, sourceType, BYTE_ARRAY_TYPE)); + ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); + byteBuffer.put(bytes); + byteBuffer.rewind(); + return byteBuffer; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java index 8f9642b0..179823f3 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.util.NumberUtils; */ final class CharacterToNumberFactory implements ConverterFactory<Character, Number> { + @Override public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) { return new CharacterToNumber<T>(targetType); } @@ -52,6 +53,7 @@ final class CharacterToNumberFactory implements ConverterFactory<Character, Numb this.targetType = targetType; } + @Override public T convert(Character source) { return NumberUtils.convertNumberToTargetClass((short) source.charValue(), this.targetType); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java index d616eeb0..ea79a065 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,14 +44,17 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter { this.conversionService = conversionService; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Collection.class, Object[].class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java index f0dfd3d6..a8071eae 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java @@ -47,16 +47,18 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Collection.class, Collection.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements( sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService); } - @SuppressWarnings("unchecked") + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java index b9913ef6..bcc51e2f 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,14 +38,17 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter { this.conversionService = conversionService; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Collection.class, Object.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java index 02b9d0eb..7537c1c3 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,14 +40,17 @@ final class CollectionToStringConverter implements ConditionalGenericConverter { this.conversionService = conversionService; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Collection.class, String.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index 86e58098..b9538a7d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.UUID; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.util.ClassUtils; /** * A specialization of {@link GenericConversionService} configured by default with @@ -31,10 +32,16 @@ import org.springframework.core.convert.converter.ConverterRegistry; * {@code ConverterRegistry} instance. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public class DefaultConversionService extends GenericConversionService { + /** Java 8's java.time package available? */ + private static final boolean jsr310Available = + ClassUtils.isPresent("java.time.ZoneId", DefaultConversionService.class.getClassLoader()); + + /** * Create a new {@code DefaultConversionService} with the set of * {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}. @@ -43,45 +50,55 @@ public class DefaultConversionService extends GenericConversionService { addDefaultConverters(this); } + // static utility methods /** * Add converters appropriate for most environments. - * @param converterRegistry the registry of converters to add to (must also be castable to ConversionService) - * @throws ClassCastException if the converterRegistry could not be cast to a ConversionService + * @param converterRegistry the registry of converters to add to (must also be castable to ConversionService, + * e.g. being a {@link ConfigurableConversionService}) + * @throws ClassCastException if the given ConverterRegistry could not be cast to a ConversionService */ public static void addDefaultConverters(ConverterRegistry converterRegistry) { addScalarConverters(converterRegistry); addCollectionConverters(converterRegistry); - addFallbackConverters(converterRegistry); + + converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry)); + if (jsr310Available) { + Jsr310ConverterRegistrar.registerZoneIdConverters(converterRegistry); + } + + converterRegistry.addConverter(new ObjectToObjectConverter()); + converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry)); + converterRegistry.addConverter(new FallbackObjectToStringConverter()); } // internal helpers private static void addScalarConverters(ConverterRegistry converterRegistry) { - ConversionService conversionService = (ConversionService) converterRegistry; - converterRegistry.addConverter(new StringToBooleanConverter()); - converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); + converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); converterRegistry.addConverterFactory(new StringToNumberConverterFactory()); converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter()); - converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); - converterRegistry.addConverter(new StringToCharacterConverter()); converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new NumberToCharacterConverter()); converterRegistry.addConverterFactory(new CharacterToNumberFactory()); + converterRegistry.addConverter(new StringToBooleanConverter()); + converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); + converterRegistry.addConverterFactory(new StringToEnumConverterFactory()); - converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter(conversionService)); + converterRegistry.addConverter(Enum.class, String.class, + new EnumToStringConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new StringToLocaleConverter()); converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter()); - converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToPropertiesConverter()); + converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToUUIDConverter()); converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter()); @@ -89,6 +106,7 @@ public class DefaultConversionService extends GenericConversionService { private static void addCollectionConverters(ConverterRegistry converterRegistry) { ConversionService conversionService = (ConversionService) converterRegistry; + converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToArrayConverter(conversionService)); @@ -109,11 +127,16 @@ public class DefaultConversionService extends GenericConversionService { converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService)); } - private static void addFallbackConverters(ConverterRegistry converterRegistry) { - ConversionService conversionService = (ConversionService) converterRegistry; - converterRegistry.addConverter(new ObjectToObjectConverter()); - converterRegistry.addConverter(new IdToEntityConverter(conversionService)); - converterRegistry.addConverter(new FallbackObjectToStringConverter()); + + /** + * Inner class to avoid a hard-coded dependency on Java 8's {@code java.time} package. + */ + private static final class Jsr310ConverterRegistrar { + + public static void registerZoneIdConverters(ConverterRegistry converterRegistry) { + converterRegistry.addConverter(new ZoneIdToTimeZoneConverter()); + converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter()); + } } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java index e391b75b..bfd1762d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java @@ -37,6 +37,7 @@ final class EnumToStringConverter implements Converter<Enum<?>, String>, Conditi this.conversionService = conversionService; } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) { if (conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) { @@ -46,6 +47,7 @@ final class EnumToStringConverter implements Converter<Enum<?>, String>, Conditi return true; } + @Override public String convert(Enum<?> source) { return source.name(); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java index 071ad3b6..90f15754 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert.support; import java.io.StringWriter; @@ -21,10 +22,12 @@ import java.util.Set; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; +import org.springframework.util.ClassUtils; /** * Simply calls {@link Object#toString()} to convert any supported Object to a String. - * Supports CharSequence, StringWriter, and any class with a String constructor or {@code valueOf(String)} method. + * Supports CharSequence, StringWriter, and any class with a String constructor or + * {@code valueOf(String)} method. * * <p>Used by the default ConversionService as a fallback if there are no other explicit * to-String converters registered. @@ -35,19 +38,24 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter; */ final class FallbackObjectToStringConverter implements ConditionalGenericConverter { + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, String.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { Class<?> sourceClass = sourceType.getObjectType(); if (String.class.equals(sourceClass)) { return false; } - return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) || - ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class); + return (CharSequence.class.isAssignableFrom(sourceClass) || + StringWriter.class.isAssignableFrom(sourceClass) || + ObjectToObjectConverter.getOfMethod(sourceClass, String.class) != null || + ClassUtils.getConstructorIfAvailable(sourceClass, String.class) != null); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return (source != null ? source.toString() : null); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index 9f78f660..613729d3 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -28,7 +28,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.core.GenericTypeResolver; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; @@ -79,33 +79,35 @@ public class GenericConversionService implements ConfigurableConversionService { // implementing ConverterRegistry + @Override public void addConverter(Converter<?, ?> converter) { - GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class); + ResolvableType[] typeInfo = getRequiredTypeInfo(converter, Converter.class); Assert.notNull(typeInfo, "Unable to the determine sourceType <S> and targetType " + "<T> which your Converter<S, T> converts between; declare these generic types."); - addConverter(new ConverterAdapter(converter, typeInfo)); + addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1])); } + @Override public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) { - GenericConverter.ConvertiblePair typeInfo = new GenericConverter.ConvertiblePair(sourceType, targetType); - addConverter(new ConverterAdapter(converter, typeInfo)); + addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType))); } + @Override public void addConverter(GenericConverter converter) { this.converters.add(converter); invalidateCache(); } + @Override public void addConverterFactory(ConverterFactory<?, ?> converterFactory) { - GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class); - if (typeInfo == null) { - throw new IllegalArgumentException("Unable to the determine sourceType <S> and " + - "targetRangeType R which your ConverterFactory<S, R> converts between; " + - "declare these generic types."); - } - addConverter(new ConverterFactoryAdapter(converterFactory, typeInfo)); + ResolvableType[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class); + Assert.notNull("Unable to the determine sourceType <S> and targetRangeType R which your " + + "ConverterFactory<S, R> converts between; declare these generic types."); + addConverter(new ConverterFactoryAdapter(converterFactory, + new ConvertiblePair(typeInfo[0].resolve(), typeInfo[1].resolve()))); } + @Override public void removeConvertible(Class<?> sourceType, Class<?> targetType) { this.converters.remove(sourceType, targetType); invalidateCache(); @@ -113,12 +115,14 @@ public class GenericConversionService implements ConfigurableConversionService { // implementing ConversionService + @Override public boolean canConvert(Class<?> sourceType, Class<?> targetType) { Assert.notNull(targetType, "targetType to convert to cannot be null"); return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null), TypeDescriptor.valueOf(targetType)); } + @Override public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "targetType to convert to cannot be null"); if (sourceType == null) { @@ -147,12 +151,14 @@ public class GenericConversionService implements ConfigurableConversionService { return (converter == NO_OP_CONVERTER); } + @Override @SuppressWarnings("unchecked") public <T> T convert(Object source, Class<T> targetType) { Assert.notNull(targetType,"The targetType to convert to cannot be null"); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType,"The targetType to convert to cannot be null"); if (sourceType == null) { @@ -256,9 +262,18 @@ public class GenericConversionService implements ConfigurableConversionService { // internal helpers - private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object converter, Class<?> genericIfc) { - Class<?>[] args = GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc); - return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null); + private ResolvableType[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) { + ResolvableType resolvableType = ResolvableType.forClass(converter.getClass()).as(genericIfc); + ResolvableType[] generics = resolvableType.getGenerics(); + if (generics.length < 2) { + return null; + } + Class<?> sourceType = generics[0].resolve(); + Class<?> targetType = generics[1].resolve(); + if (sourceType == null || targetType == null) { + return null; + } + return generics; } private void invalidateCache() { @@ -301,25 +316,36 @@ public class GenericConversionService implements ConfigurableConversionService { private final ConvertiblePair typeInfo; - public ConverterAdapter(Converter<?, ?> converter, ConvertiblePair typeInfo) { + private final ResolvableType targetType; + + public ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) { this.converter = (Converter<Object, Object>) converter; - this.typeInfo = typeInfo; + this.typeInfo = new ConvertiblePair(sourceType.resolve(Object.class), targetType.resolve(Object.class)); + this.targetType = targetType; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(this.typeInfo); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + // Check raw type first... if (!this.typeInfo.getTargetType().equals(targetType.getObjectType())) { return false; } - if (this.converter instanceof ConditionalConverter) { - return ((ConditionalConverter) this.converter).matches(sourceType, targetType); + // Full check for complex generic type match required? + ResolvableType rt = targetType.getResolvableType(); + if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) && + !this.targetType.hasUnresolvableGenerics()) { + return false; } - return true; + return !(this.converter instanceof ConditionalConverter) || + ((ConditionalConverter) this.converter).matches(sourceType, targetType); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return convertNullSource(sourceType, targetType); @@ -349,10 +375,12 @@ public class GenericConversionService implements ConfigurableConversionService { this.typeInfo = typeInfo; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(this.typeInfo); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { boolean matches = true; if (this.converterFactory instanceof ConditionalConverter) { @@ -367,6 +395,7 @@ public class GenericConversionService implements ConfigurableConversionService { return matches; } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return convertNullSource(sourceType, targetType); @@ -395,6 +424,7 @@ public class GenericConversionService implements ConfigurableConversionService { this.targetType = targetType; } + @Override public boolean equals(Object other) { if (this == other) { return true; @@ -518,7 +548,7 @@ public class GenericConversionService implements ConfigurableConversionService { Class<?> candidate = hierarchy.get(i); candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate)); Class<?> superclass = candidate.getSuperclass(); - if (candidate.getSuperclass() != null && superclass != Object.class) { + if (superclass != null && superclass != Object.class) { addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited); } for (Class<?> implementedInterface : candidate.getInterfaces()) { @@ -533,6 +563,7 @@ public class GenericConversionService implements ConfigurableConversionService { private void addToClassHierarchy(int index, Class<?> type, boolean asArray, List<Class<?>> hierarchy, Set<Class<?>> visited) { + if (asArray) { type = Array.newInstance(type, 0).getClass(); } @@ -601,10 +632,12 @@ public class GenericConversionService implements ConfigurableConversionService { this.name = name; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return null; } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source; } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java index da30e0d3..35f1d223 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java @@ -48,16 +48,19 @@ final class IdToEntityConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { Method finder = getFinder(targetType.getType()); return (finder != null && this.conversionService.canConvert(sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0]))); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java index 98b37dea..cd99976c 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java @@ -49,14 +49,17 @@ final class MapToMapConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Map.class, Map.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return canConvertKey(sourceType, targetType) && canConvertValue(sourceType, targetType); } + @Override @SuppressWarnings("unchecked") public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java index b272abc6..d79435d0 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.springframework.core.convert.converter.Converter; */ final class NumberToCharacterConverter implements Converter<Number, Character> { + @Override public Character convert(Number source) { return (char) source.shortValue(); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java index 240880d6..b98af70e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java @@ -43,10 +43,12 @@ import org.springframework.util.NumberUtils; final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>, ConditionalConverter { + @Override public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) { return new NumberToNumber<T>(targetType); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return !sourceType.equals(targetType); } @@ -59,6 +61,7 @@ final class NumberToNumberConverterFactory implements ConverterFactory<Number, N this.targetType = targetType; } + @Override public T convert(Number source) { return NumberUtils.convertNumberToTargetClass(source, this.targetType); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java index 9cdbb562..1531926a 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java @@ -41,14 +41,17 @@ final class ObjectToArrayConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Object[].class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java index 7890cdb1..8900ba0c 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java @@ -43,15 +43,17 @@ final class ObjectToCollectionConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Collection.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService); } - @SuppressWarnings("unchecked") + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java index 0448e582..a7df4866 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,13 +29,14 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** - * Generic Converter that attempts to convert a source Object to a target type + * Generic converter that attempts to convert a source Object to a target type * by delegating to methods on the target type. * - * <p>Calls the static {@code valueOf(sourceType)} method on the target type - * to perform the conversion, if such a method exists. Else calls the target type's - * Constructor that accepts a single sourceType argument, if such a Constructor exists. - * Else throws a ConversionFailedException. + * <p>Calls a static {@code valueOf(sourceType)} or Java 8 style {@code of|from(sourceType)} + * method on the target type to perform the conversion, if such a method exists. Otherwise, + * it checks for a {@code to[targetType.simpleName]} method on the source type calls + * the target type's constructor that accepts a single {@code sourceType} argument, if such + * a constructor exists. If neither strategy works, it throws a ConversionFailedException. * * @author Keith Donald * @author Juergen Hoeller @@ -43,36 +44,46 @@ import org.springframework.util.ReflectionUtils; */ final class ObjectToObjectConverter implements ConditionalGenericConverter { + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { if (sourceType.getType().equals(targetType.getType())) { // no conversion required return false; } - return hasValueOfMethodOrConstructor(targetType.getType(), sourceType.getType()); + return (String.class.equals(targetType.getType()) ? + (ClassUtils.getConstructorIfAvailable(String.class, sourceType.getType()) != null) : + hasToMethodOrOfMethodOrConstructor(targetType.getType(), sourceType.getType())); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } Class<?> sourceClass = sourceType.getType(); Class<?> targetClass = targetType.getType(); - Method method = getValueOfMethodOn(targetClass, sourceClass); try { - if (method != null) { - ReflectionUtils.makeAccessible(method); - return method.invoke(null, source); - } - else { - Constructor<?> constructor = getConstructor(targetClass, sourceClass); - if (constructor != null) { - return constructor.newInstance(source); + if (!String.class.equals(targetClass)) { + Method method = getToMethod(targetClass, sourceClass); + if (method != null) { + ReflectionUtils.makeAccessible(method); + return method.invoke(source); + } + method = getOfMethod(targetClass, sourceClass); + if (method != null) { + ReflectionUtils.makeAccessible(method); + return method.invoke(null, source); } } + Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(targetClass, sourceClass); + if (constructor != null) { + return constructor.newInstance(source); + } } catch (InvocationTargetException ex) { throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException()); @@ -80,20 +91,31 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter { catch (Throwable ex) { throw new ConversionFailedException(sourceType, targetType, source, ex); } - throw new IllegalStateException("No static valueOf(" + sourceClass.getName() + + throw new IllegalStateException("No static valueOf/of/from(" + sourceClass.getName() + ") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName()); } - static boolean hasValueOfMethodOrConstructor(Class<?> clazz, Class<?> sourceParameterType) { - return getValueOfMethodOn(clazz, sourceParameterType) != null || getConstructor(clazz, sourceParameterType) != null; + + private static boolean hasToMethodOrOfMethodOrConstructor(Class<?> targetClass, Class<?> sourceClass) { + return (getToMethod(targetClass, sourceClass) != null || + getOfMethod(targetClass, sourceClass) != null || + ClassUtils.getConstructorIfAvailable(targetClass, sourceClass) != null); } - private static Method getValueOfMethodOn(Class<?> clazz, Class<?> sourceParameterType) { - return ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType); + private static Method getToMethod(Class<?> targetClass, Class<?> sourceClass) { + Method method = ClassUtils.getMethodIfAvailable(sourceClass, "to" + targetClass.getSimpleName()); + return (method != null && targetClass.equals(method.getReturnType()) ? method : null); } - private static Constructor<?> getConstructor(Class<?> clazz, Class<?> sourceParameterType) { - return ClassUtils.getConstructorIfAvailable(clazz, sourceParameterType); + static Method getOfMethod(Class<?> targetClass, Class<?> sourceClass) { + Method method = ClassUtils.getStaticMethod(targetClass, "valueOf", sourceClass); + if (method == null) { + method = ClassUtils.getStaticMethod(targetClass, "of", sourceClass); + if (method == null) { + method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass); + } + } + return method; } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java index 420183b3..e5fd8419 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java @@ -26,6 +26,7 @@ import org.springframework.core.convert.converter.Converter; */ final class ObjectToStringConverter implements Converter<Object, String> { + @Override public String convert(Object source) { return source.toString(); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java index f4962d01..4d2124ad 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java @@ -31,6 +31,7 @@ import org.springframework.core.convert.converter.Converter; */ final class PropertiesToStringConverter implements Converter<Properties, String> { + @Override public String convert(Properties source) { try { ByteArrayOutputStream os = new ByteArrayOutputStream(256); diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java index 4bf32af3..af209148 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,14 +40,17 @@ final class StringToArrayConverter implements ConditionalGenericConverter { this.conversionService = conversionService; } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, Object[].class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()); } + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java index 1e23ee61..1d8bc61e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ final class StringToBooleanConverter implements Converter<String, Boolean> { falseValues.add("0"); } + @Override public Boolean convert(String source) { String value = source.trim(); if ("".equals(value)) { diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java index 3e379164..7a076d33 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.core.convert.converter.Converter; */ final class StringToCharacterConverter implements Converter<String, Character> { + @Override public Character convert(String source) { if (source.length() == 0) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java index b16c2462..15d7df11 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java @@ -45,16 +45,18 @@ final class StringToCollectionConverter implements ConditionalGenericConverter { } + @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, Collection.class)); } + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return (targetType.getElementTypeDescriptor() == null || this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor())); } - @SuppressWarnings("unchecked") + @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java index e015afb3..923239b9 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java @@ -28,6 +28,7 @@ import org.springframework.core.convert.converter.ConverterFactory; @SuppressWarnings({"unchecked", "rawtypes"}) final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> { + @Override public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) { Class<?> enumType = targetType; while (enumType != null && !enumType.isEnum()) { @@ -49,6 +50,7 @@ final class StringToEnumConverterFactory implements ConverterFactory<String, Enu this.enumType = enumType; } + @Override public T convert(String source) { if (source.length() == 0) { // It's an empty enum identifier: reset the enum value to null. diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java index 3ca91385..c6c22ea2 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.util.StringUtils; */ final class StringToLocaleConverter implements Converter<String, Locale> { + @Override public Locale convert(String source) { return StringUtils.parseLocaleString(source); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java index 8bb0d646..506dcec9 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.springframework.util.NumberUtils; */ final class StringToNumberConverterFactory implements ConverterFactory<String, Number> { + @Override public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) { return new StringToNumber<T>(targetType); } @@ -52,6 +53,7 @@ final class StringToNumberConverterFactory implements ConverterFactory<String, N this.targetType = targetType; } + @Override public T convert(String source) { if (source.length() == 0) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java index e97cae67..83c6c13f 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.core.convert.converter.Converter; */ final class StringToPropertiesConverter implements Converter<String, Properties> { + @Override public Properties convert(String source) { try { Properties props = new Properties(); diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java index db5a3ebf..48e6a0ae 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java @@ -29,6 +29,7 @@ import org.springframework.util.StringUtils; */ final class StringToUUIDConverter implements Converter<String, UUID> { + @Override public UUID convert(String source) { if (StringUtils.hasLength(source)) { return UUID.fromString(source.trim()); diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java new file mode 100644 index 00000000..e0416ea4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core.convert.support; + +import java.time.ZoneId; +import java.util.TimeZone; + +import org.springframework.core.convert.converter.Converter; + +/** + * Simple converter from Java 8's {@link java.time.ZoneId} to {@link java.util.TimeZone}. + * + * <p>Note that Spring's default ConversionService setup understands the 'from'/'to' convention + * that the JSR-310 {@code java.time} package consistently uses. That convention is implemented + * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters. + * It covers {@link java.util.TimeZone#toZoneId()} as well, and also + * {@link java.util.Date#from(java.time.Instant)} and {@link java.util.Date#toInstant()}. + * + * @author Juergen Hoeller + * @since 4.0 + * @see TimeZone#getTimeZone(java.time.ZoneId) + */ +final class ZoneIdToTimeZoneConverter implements Converter<ZoneId, TimeZone> { + + @Override + public TimeZone convert(ZoneId source) { + return TimeZone.getTimeZone(source); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java new file mode 100644 index 00000000..02d6017d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core.convert.support; + +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import org.springframework.core.convert.converter.Converter; + +/** + * Simple converter from Java 8's {@link java.time.ZonedDateTime} to {@link java.util.Calendar}. + * + * <p>Note that Spring's default ConversionService setup understands the 'from'/'to' convention + * that the JSR-310 {@code java.time} package consistently uses. That convention is implemented + * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters. + * It covers {@link java.util.GregorianCalendar#toZonedDateTime()} as well, and also + * {@link java.util.Date#from(java.time.Instant)} and {@link java.util.Date#toInstant()}. + * + * @author Juergen Hoeller + * @since 4.0.1 + * @see java.util.GregorianCalendar#from(java.time.ZonedDateTime) + */ +final class ZonedDateTimeToCalendarConverter implements Converter<ZonedDateTime, Calendar> { + + @Override + public Calendar convert(ZonedDateTime source) { + return GregorianCalendar.from(source); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java b/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java deleted file mode 100644 index 4a278f92..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.util.Assert; -import org.springframework.util.CachingMapDecorator; -import org.springframework.util.ClassUtils; - -/** - * Abstract base class for {@link LabeledEnumResolver} implementations, - * caching all retrieved {@link LabeledEnum} instances. - * - * <p>Subclasses need to implement the template method - * {@link #findLabeledEnums(Class)}. - * - * @author Keith Donald - * @author Juergen Hoeller - * @since 1.2.2 - * @see #findLabeledEnums(Class) - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -public abstract class AbstractCachingLabeledEnumResolver implements LabeledEnumResolver { - - protected transient final Log logger = LogFactory.getLog(getClass()); - - private final LabeledEnumCache labeledEnumCache = new LabeledEnumCache(); - - - public Set<LabeledEnum> getLabeledEnumSet(Class type) throws IllegalArgumentException { - return new TreeSet<LabeledEnum>(getLabeledEnumMap(type).values()); - } - - public Map<Comparable, LabeledEnum> getLabeledEnumMap(Class type) throws IllegalArgumentException { - Assert.notNull(type, "No type specified"); - return this.labeledEnumCache.get(type); - } - - public LabeledEnum getLabeledEnumByCode(Class type, Comparable code) throws IllegalArgumentException { - Assert.notNull(code, "No enum code specified"); - Map<Comparable, LabeledEnum> typeEnums = getLabeledEnumMap(type); - LabeledEnum codedEnum = typeEnums.get(code); - if (codedEnum == null) { - throw new IllegalArgumentException( - "No enumeration with code '" + code + "'" + " of type [" + type.getName() + - "] exists: this is likely a configuration error. " + - "Make sure the code value matches a valid instance's code property!"); - } - return codedEnum; - } - - public LabeledEnum getLabeledEnumByLabel(Class type, String label) throws IllegalArgumentException { - Map<Comparable, LabeledEnum> typeEnums = getLabeledEnumMap(type); - for (LabeledEnum value : typeEnums.values()) { - if (value.getLabel().equalsIgnoreCase(label)) { - return value; - } - } - throw new IllegalArgumentException( - "No enumeration with label '" + label + "' of type [" + type + - "] exists: this is likely a configuration error. " + - "Make sure the label string matches a valid instance's label property!"); - } - - - /** - * Template method to be implemented by subclasses. - * Supposed to find all LabeledEnum instances for the given type. - * @param type the enum type - * @return the Set of LabeledEnum instances - * @see org.springframework.core.enums.LabeledEnum - */ - protected abstract Set<LabeledEnum> findLabeledEnums(Class type); - - - /** - * Inner cache class that implements lazy building of LabeledEnum Maps. - */ - @SuppressWarnings("serial") - private class LabeledEnumCache extends CachingMapDecorator<Class, Map<Comparable, LabeledEnum>> { - - public LabeledEnumCache() { - super(true); - } - - @Override - protected Map<Comparable, LabeledEnum> create(Class key) { - Set<LabeledEnum> typeEnums = findLabeledEnums(key); - if (typeEnums == null || typeEnums.isEmpty()) { - throw new IllegalArgumentException( - "Unsupported labeled enumeration type '" + key + "': " + - "make sure you've properly defined this enumeration! " + - "If it is static, are the class and its fields public/static/final?"); - } - Map<Comparable, LabeledEnum> typeEnumMap = new HashMap<Comparable, LabeledEnum>(typeEnums.size()); - for (LabeledEnum labeledEnum : typeEnums) { - typeEnumMap.put(labeledEnum.getCode(), labeledEnum); - } - return Collections.unmodifiableMap(typeEnumMap); - } - - @Override - protected boolean useWeakValue(Class key, Map<Comparable, LabeledEnum> value) { - if (!ClassUtils.isCacheSafe(key, AbstractCachingLabeledEnumResolver.this.getClass().getClassLoader())) { - if (logger != null && logger.isDebugEnabled()) { - logger.debug("Not strongly caching class [" + key.getName() + "] because it is not cache-safe"); - } - return true; - } - else { - return false; - } - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java deleted file mode 100644 index cee9535c..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -/** - * Base class for labeled enum instances that aren't static. - * - * @author Keith Donald - * @since 1.2.6 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -@SuppressWarnings("serial") -public abstract class AbstractGenericLabeledEnum extends AbstractLabeledEnum { - - /** - * A descriptive label for the enum. - */ - private final String label; - - - /** - * Create a new StaticLabeledEnum instance. - * @param label the label; if {@code null}), the enum's code - * will be used as label - */ - protected AbstractGenericLabeledEnum(String label) { - this.label = label; - } - - - public String getLabel() { - if (this.label != null) { - return label; - } - else { - return getCode().toString(); - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java deleted file mode 100644 index 3c2860f7..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -/** - * Abstract base superclass for LabeledEnum implementations. - * - * @author Keith Donald - * @author Juergen Hoeller - * @author Sam Brannen - * @since 1.2.2 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -@SuppressWarnings("serial") -public abstract class AbstractLabeledEnum implements LabeledEnum { - - /** - * Create a new AbstractLabeledEnum instance. - */ - protected AbstractLabeledEnum() { - } - - public Class getType() { - // Could be coded as getClass().isAnonymousClass() on JDK 1.5 - boolean isAnonymous = (getClass().getDeclaringClass() == null && getClass().getName().indexOf('$') != -1); - return (isAnonymous ? getClass().getSuperclass() : getClass()); - } - - public int compareTo(Object obj) { - if (!(obj instanceof LabeledEnum)) { - throw new ClassCastException("You may only compare LabeledEnums"); - } - LabeledEnum that = (LabeledEnum) obj; - if (!this.getType().equals(that.getType())) { - throw new ClassCastException("You may only compare LabeledEnums of the same type"); - } - return this.getCode().compareTo(that.getCode()); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof LabeledEnum)) { - return false; - } - LabeledEnum other = (LabeledEnum) obj; - return (this.getType().equals(other.getType()) && this.getCode().equals(other.getCode())); - } - - @Override - public int hashCode() { - return (getType().hashCode() * 29 + getCode().hashCode()); - } - - @Override - public String toString() { - return getLabel(); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java deleted file mode 100644 index dc2325bf..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -import java.io.Serializable; -import java.util.Comparator; - -import org.springframework.util.comparator.CompoundComparator; -import org.springframework.util.comparator.NullSafeComparator; - -/** - * An interface for objects that represent a labeled enumeration. - * Each such enum instance has the following characteristics: - * - * <ul> - * <li>A type that identifies the enum's class. - * For example: {@code com.mycompany.util.FileFormat}.</li> - * - * <li>A code that uniquely identifies the enum within the context of its type. - * For example: "CSV". Different classes of codes are possible - * (e.g., Character, Integer, String).</li> - * - * <li>A descriptive label. For example: "the CSV File Format".</li> - * </ul> - * - * @author Keith Donald - * @since 1.2.2 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -public interface LabeledEnum extends Comparable, Serializable { - - /** - * Return this enumeration's type. - */ - Class getType(); - - /** - * Return this enumeration's code. - * <p>Each code should be unique within enumerations of the same type. - */ - Comparable getCode(); - - /** - * Return a descriptive, optional label. - */ - String getLabel(); - - - // Constants for standard enum ordering (Comparator implementations) - - /** - * Shared Comparator instance that sorts enumerations by {@code CODE_ORDER}. - */ - Comparator CODE_ORDER = new Comparator() { - public int compare(Object o1, Object o2) { - Comparable c1 = ((LabeledEnum) o1).getCode(); - Comparable c2 = ((LabeledEnum) o2).getCode(); - return c1.compareTo(c2); - } - }; - - /** - * Shared Comparator instance that sorts enumerations by {@code LABEL_ORDER}. - */ - Comparator LABEL_ORDER = new Comparator() { - public int compare(Object o1, Object o2) { - LabeledEnum e1 = (LabeledEnum) o1; - LabeledEnum e2 = (LabeledEnum) o2; - Comparator comp = new NullSafeComparator(String.CASE_INSENSITIVE_ORDER, true); - return comp.compare(e1.getLabel(), e2.getLabel()); - } - }; - - /** - * Shared Comparator instance that sorts enumerations by {@code LABEL_ORDER}, - * then {@code CODE_ORDER}. - */ - Comparator DEFAULT_ORDER = - new CompoundComparator(new Comparator[] { LABEL_ORDER, CODE_ORDER }); - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java b/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java deleted file mode 100644 index 7b374a70..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -import java.util.Map; -import java.util.Set; - -/** - * Interface for looking up {@code LabeledEnum} instances. - * - * @author Keith Donald - * @author Juergen Hoeller - * @since 1.2.2 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -public interface LabeledEnumResolver { - - /** - * Return a set of enumerations of a particular type. Each element in the - * set should be an instance of LabeledEnum. - * @param type the enum type - * @return a set of localized enumeration instances for the provided type - * @throws IllegalArgumentException if the type is not supported - */ - public Set getLabeledEnumSet(Class type) throws IllegalArgumentException; - - /** - * Return a map of enumerations of a particular type. Each element in the - * map should be a key/value pair, where the key is the enum code, and the - * value is the {@code LabeledEnum} instance. - * @param type the enum type - * @return a Map of localized enumeration instances, - * with enum code as key and {@code LabeledEnum} instance as value - * @throws IllegalArgumentException if the type is not supported - */ - public Map getLabeledEnumMap(Class type) throws IllegalArgumentException; - - /** - * Resolve a single {@code LabeledEnum} by its identifying code. - * @param type the enum type - * @param code the enum code - * @return the enum - * @throws IllegalArgumentException if the code did not map to a valid instance - */ - public LabeledEnum getLabeledEnumByCode(Class type, Comparable code) throws IllegalArgumentException; - - /** - * Resolve a single {@code LabeledEnum} by its identifying code. - * @param type the enum type - * @param label the enum label - * @return the enum - * @throws IllegalArgumentException if the label did not map to a valid instance - */ - public LabeledEnum getLabeledEnumByLabel(Class type, String label) throws IllegalArgumentException; - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java deleted file mode 100644 index cb70971a..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -import org.springframework.util.Assert; - -/** - * Implementation of LabeledEnum which uses a letter as the code type. - * - * <p>Should almost always be subclassed, but for some simple situations it may be - * used directly. Note that you will not be able to use unique type-based functionality - * like {@code LabeledEnumResolver.getLabeledEnumSet(type)} in this case. - * - * @author Keith Donald - * @since 1.2.2 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -@SuppressWarnings("serial") -public class LetterCodedLabeledEnum extends AbstractGenericLabeledEnum { - - /** - * The unique code of this enum. - */ - private final Character code; - - - /** - * Create a new LetterCodedLabeledEnum instance. - * @param code the letter code - * @param label the label (can be {@code null}) - */ - public LetterCodedLabeledEnum(char code, String label) { - super(label); - Assert.isTrue(Character.isLetter(code), - "The code '" + code + "' is invalid: it must be a letter"); - this.code = new Character(code); - } - - - public Comparable getCode() { - return code; - } - - /** - * Return the letter code of this LabeledEnum instance. - */ - public char getLetterCode() { - return ((Character) getCode()).charValue(); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java deleted file mode 100644 index 0c5e3fae..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -/** - * Implementation of LabeledEnum which uses Short as the code type. - * - * <p>Should almost always be subclassed, but for some simple situations it may be - * used directly. Note that you will not be able to use unique type-based functionality - * like {@code LabeledEnumResolver.getLabeledEnumSet(type)} in this case. - * - * @author Keith Donald - * @since 1.2.2 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -@SuppressWarnings("serial") -public class ShortCodedLabeledEnum extends AbstractGenericLabeledEnum { - - /** - * The unique code of this enum. - */ - private final Short code; - - - /** - * Create a new ShortCodedLabeledEnum instance. - * @param code the short code - * @param label the label (can be {@code null}) - */ - public ShortCodedLabeledEnum(int code, String label) { - super(label); - this.code = new Short((short) code); - } - - - public Comparable getCode() { - return code; - } - - /** - * Return the short code of this LabeledEnum instance. - */ - public short getShortCode() { - return ((Short) getCode()).shortValue(); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java deleted file mode 100644 index e6440cc3..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -/** - * Base class for static type-safe labeled enum instances. - * - * Usage example: - * - * <pre> - * public class FlowSessionStatus extends StaticLabeledEnum { - * - * // public static final instances! - * public static FlowSessionStatus CREATED = new FlowSessionStatus(0, "Created"); - * public static FlowSessionStatus ACTIVE = new FlowSessionStatus(1, "Active"); - * public static FlowSessionStatus PAUSED = new FlowSessionStatus(2, "Paused"); - * public static FlowSessionStatus SUSPENDED = new FlowSessionStatus(3, "Suspended"); - * public static FlowSessionStatus ENDED = new FlowSessionStatus(4, "Ended"); - * - * // private constructor! - * private FlowSessionStatus(int code, String label) { - * super(code, label); - * } - * - * // custom behavior - * }</pre> - * - * @author Keith Donald - * @since 1.2.6 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -@SuppressWarnings("serial") -public abstract class StaticLabeledEnum extends AbstractLabeledEnum { - - /** - * The unique code of the enum. - */ - private final Short code; - - /** - * A descriptive label for the enum. - */ - private final transient String label; - - - /** - * Create a new StaticLabeledEnum instance. - * @param code the short code - * @param label the label (can be {@code null}) - */ - protected StaticLabeledEnum(int code, String label) { - this.code = new Short((short) code); - if (label != null) { - this.label = label; - } - else { - this.label = this.code.toString(); - } - } - - public Comparable getCode() { - return code; - } - - public String getLabel() { - return label; - } - - /** - * Return the code of this LabeledEnum instance as a short. - */ - public short shortValue() { - return ((Number) getCode()).shortValue(); - } - - - //--------------------------------------------------------------------- - // Serialization support - //--------------------------------------------------------------------- - - /** - * Return the resolved type safe static enum instance. - */ - protected Object readResolve() { - return StaticLabeledEnumResolver.instance().getLabeledEnumByCode(getType(), getCode()); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java b/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java deleted file mode 100644 index 3d9f78a4..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.Set; -import java.util.TreeSet; - -import org.springframework.util.Assert; - -/** - * {@link LabeledEnumResolver} that resolves statically defined enumerations. - * Static implies all enum instances were defined within Java code, - * implementing the type-safe enum pattern. - * - * @author Keith Donald - * @author Juergen Hoeller - * @since 1.2.2 - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -public class StaticLabeledEnumResolver extends AbstractCachingLabeledEnumResolver { - - /** - * Shared {@code StaticLabeledEnumResolver} singleton instance. - */ - private static final StaticLabeledEnumResolver INSTANCE = new StaticLabeledEnumResolver(); - - - /** - * Return the shared {@code StaticLabeledEnumResolver} singleton instance. - * Mainly for resolving unique StaticLabeledEnum references on deserialization. - * @see StaticLabeledEnum - */ - public static StaticLabeledEnumResolver instance() { - return INSTANCE; - } - - - @Override - protected Set<LabeledEnum> findLabeledEnums(Class type) { - Set<LabeledEnum> typeEnums = new TreeSet<LabeledEnum>(); - for (Field field : type.getFields()) { - if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers())) { - if (type.isAssignableFrom(field.getType())) { - try { - Object value = field.get(null); - Assert.isTrue(value instanceof LabeledEnum, "Field value must be a LabeledEnum instance"); - typeEnums.add((LabeledEnum) value); - } - catch (IllegalAccessException ex) { - logger.warn("Unable to access field value: " + field, ex); - } - } - } - } - return typeEnums; - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java deleted file mode 100644 index d0a936ef..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.core.enums; - -import org.springframework.util.Assert; - -/** - * Implementation of LabeledEnum which uses a String as the code type. - * - * <p>Should almost always be subclassed, but for some simple situations it may be - * used directly. Note that you will not be able to use unique type-based - * functionality like {@code LabeledEnumResolver.getLabeledEnumSet(type)} in this case. - * - * @author Keith Donald - * @author Juergen Hoeller - * @since 1.2.2 - * @see org.springframework.core.enums.LabeledEnumResolver#getLabeledEnumSet(Class) - * @deprecated as of Spring 3.0, in favor of Java 5 enums. - */ -@Deprecated -@SuppressWarnings("serial") -public class StringCodedLabeledEnum extends AbstractGenericLabeledEnum { - - /** - * The unique code of this enum. - */ - private final String code; - - - /** - * Create a new StringCodedLabeledEnum instance. - * @param code the String code - * @param label the label (can be {@code null}) - */ - public StringCodedLabeledEnum(String code, String label) { - super(label); - Assert.notNull(code, "'code' must not be null"); - this.code = code; - } - - - public Comparable getCode() { - return this.code; - } - - /** - * Return the String code of this LabeledEnum instance. - */ - public String getStringCode() { - return (String) getCode(); - } - -} diff --git a/spring-core/src/main/java/org/springframework/core/enums/package-info.java b/spring-core/src/main/java/org/springframework/core/enums/package-info.java deleted file mode 100644 index ac720538..00000000 --- a/spring-core/src/main/java/org/springframework/core/enums/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ - -/** - * - * Interfaces and classes for type-safe enum support on JDK >= 1.3. - * This enum abstraction support codes and labels. - * - */ -package org.springframework.core.enums; - diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index 786443be..b7918aee 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -223,6 +223,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { // Implementation of ConfigurableEnvironment interface //--------------------------------------------------------------------- + @Override public String[] getActiveProfiles() { return StringUtils.toStringArray(doGetActiveProfiles()); } @@ -245,6 +246,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return this.activeProfiles; } + @Override public void setActiveProfiles(String... profiles) { Assert.notNull(profiles, "Profile array must not be null"); this.activeProfiles.clear(); @@ -254,6 +256,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } + @Override public void addActiveProfile(String profile) { if (this.logger.isDebugEnabled()) { this.logger.debug(format("Activating profile '%s'", profile)); @@ -264,6 +267,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } + @Override public String[] getDefaultProfiles() { return StringUtils.toStringArray(doGetDefaultProfiles()); } @@ -298,6 +302,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { * @see #AbstractEnvironment() * @see #getReservedDefaultProfiles() */ + @Override public void setDefaultProfiles(String... profiles) { Assert.notNull(profiles, "Profile array must not be null"); this.defaultProfiles.clear(); @@ -307,6 +312,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } + @Override public boolean acceptsProfiles(String... profiles) { Assert.notEmpty(profiles, "Must specify at least one profile"); for (String profile : profiles) { @@ -352,10 +358,12 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } + @Override public MutablePropertySources getPropertySources() { return this.propertySources; } + @Override @SuppressWarnings("unchecked") public Map<String, Object> getSystemEnvironment() { if (suppressGetenvAccess()) { @@ -399,6 +407,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME); } + @Override @SuppressWarnings("unchecked") public Map<String, Object> getSystemProperties() { try { @@ -424,6 +433,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } + @Override public void merge(ConfigurableEnvironment parent) { for (PropertySource<?> ps : parent.getPropertySources()) { if (!this.propertySources.contains(ps.getName())) { @@ -446,34 +456,42 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { // Implementation of ConfigurablePropertyResolver interface //--------------------------------------------------------------------- + @Override public ConfigurableConversionService getConversionService() { return this.propertyResolver.getConversionService(); } + @Override public void setConversionService(ConfigurableConversionService conversionService) { this.propertyResolver.setConversionService(conversionService); } + @Override public void setPlaceholderPrefix(String placeholderPrefix) { this.propertyResolver.setPlaceholderPrefix(placeholderPrefix); } + @Override public void setPlaceholderSuffix(String placeholderSuffix) { this.propertyResolver.setPlaceholderSuffix(placeholderSuffix); } + @Override public void setValueSeparator(String valueSeparator) { this.propertyResolver.setValueSeparator(valueSeparator); } + @Override public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) { this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders); } + @Override public void setRequiredProperties(String... requiredProperties) { this.propertyResolver.setRequiredProperties(requiredProperties); } + @Override public void validateRequiredProperties() throws MissingRequiredPropertiesException { this.propertyResolver.validateRequiredProperties(); } @@ -488,38 +506,47 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return this.propertyResolver.containsProperty(key); } + @Override public String getProperty(String key) { return this.propertyResolver.getProperty(key); } + @Override public String getProperty(String key, String defaultValue) { return this.propertyResolver.getProperty(key, defaultValue); } + @Override public <T> T getProperty(String key, Class<T> targetType) { return this.propertyResolver.getProperty(key, targetType); } + @Override public <T> T getProperty(String key, Class<T> targetType, T defaultValue) { return this.propertyResolver.getProperty(key, targetType, defaultValue); } + @Override public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) { return this.propertyResolver.getPropertyAsClass(key, targetType); } + @Override public String getRequiredProperty(String key) throws IllegalStateException { return this.propertyResolver.getRequiredProperty(key); } + @Override public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException { return this.propertyResolver.getRequiredProperty(key, targetType); } + @Override public String resolvePlaceholders(String text) { return this.propertyResolver.resolvePlaceholders(text); } + @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { return this.propertyResolver.resolveRequiredPlaceholders(text); } diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java index 4b02991a..b5f18271 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java @@ -55,10 +55,12 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe private final Set<String> requiredProperties = new LinkedHashSet<String>(); + @Override public ConfigurableConversionService getConversionService() { return this.conversionService; } + @Override public void setConversionService(ConfigurableConversionService conversionService) { this.conversionService = conversionService; } @@ -68,6 +70,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe * <p>The default is "${". * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX */ + @Override public void setPlaceholderPrefix(String placeholderPrefix) { this.placeholderPrefix = placeholderPrefix; } @@ -77,6 +80,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe * <p>The default is "}". * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX */ + @Override public void setPlaceholderSuffix(String placeholderSuffix) { this.placeholderSuffix = placeholderSuffix; } @@ -88,6 +92,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe * <p>The default is ":". * @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR */ + @Override public void setValueSeparator(String valueSeparator) { this.valueSeparator = valueSeparator; } @@ -101,16 +106,19 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe * <p>The default is {@code false}. * @since 3.2 */ + @Override public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) { this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders; } + @Override public void setRequiredProperties(String... requiredProperties) { for (String key : requiredProperties) { this.requiredProperties.add(key); } } + @Override public void validateRequiredProperties() { MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); for (String key : this.requiredProperties) { @@ -124,16 +132,19 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe } + @Override public String getProperty(String key, String defaultValue) { String value = getProperty(key); return (value != null ? value : defaultValue); } + @Override public <T> T getProperty(String key, Class<T> targetType, T defaultValue) { T value = getProperty(key, targetType); return (value != null ? value : defaultValue); } + @Override public String getRequiredProperty(String key) throws IllegalStateException { String value = getProperty(key); if (value == null) { @@ -142,6 +153,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe return value; } + @Override public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException { T value = getProperty(key, valueType); if (value == null) { @@ -150,6 +162,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe return value; } + @Override public String resolvePlaceholders(String text) { if (this.nonStrictHelper == null) { this.nonStrictHelper = createPlaceholderHelper(true); @@ -157,6 +170,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe return doResolvePlaceholders(text, this.nonStrictHelper); } + @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { this.strictHelper = createPlaceholderHelper(false); @@ -188,6 +202,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { + @Override public String resolvePlaceholder(String placeholderName) { return getPropertyAsRawString(placeholderName); } diff --git a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java index 575cd359..c71630fd 100644 --- a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java @@ -185,7 +185,7 @@ import org.springframework.util.StringUtils; * @see SimpleCommandLinePropertySource * @see JOptCommandLinePropertySource */ -public abstract class CommandLinePropertySource<T> extends PropertySource<T> { +public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> { /** The default name given to {@link CommandLinePropertySource} instances: {@value} */ public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; diff --git a/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java index c6742025..dde7a4ea 100644 --- a/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java @@ -63,6 +63,7 @@ public abstract class EnumerablePropertySource<T> extends PropertySource<T> { * {@link #getPropertyNames()} array. * @param name the name of the property to find */ + @Override public boolean containsProperty(String name) { Assert.notNull(name, "Property name must not be null"); for (String candidate : getPropertyNames()) { diff --git a/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java index 61ed06a6..161cccd3 100644 --- a/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; import joptsimple.OptionSet; +import joptsimple.OptionSpec; import org.springframework.util.Assert; @@ -43,10 +44,11 @@ import org.springframework.util.Assert; * * See {@link CommandLinePropertySource} for complete general usage examples. * - * <p>Requires JOpt version 3.0 or higher. Tested against JOpt up until 4.6. + * <p>Requires JOpt version 4.3 or higher. Tested against JOpt up until 4.6. * * @author Chris Beams * @author Juergen Hoeller + * @author Dave Syer * @since 3.1 * @see CommandLinePropertySource * @see joptsimple.OptionParser @@ -79,6 +81,19 @@ public class JOptCommandLinePropertySource extends CommandLinePropertySource<Opt } @Override + public String[] getPropertyNames() { + List<String> names = new ArrayList<String>(); + for (OptionSpec<?> spec : this.source.specs()) { + List<String> aliases = new ArrayList<String>(spec.options()); + if (!aliases.isEmpty()) { + // Only the longest name is used for enumerating + names.add(aliases.get(aliases.size() - 1)); + } + } + return names.toArray(new String[names.size()]); + } + + @Override public List<String> getOptionValues(String name) { List<?> argValues = this.source.valuesOf(name); List<String> stringArgValues = new ArrayList<String>(); diff --git a/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java b/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java index 507704bd..1587833c 100644 --- a/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java +++ b/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java @@ -17,12 +17,12 @@ package org.springframework.core.env; import java.util.Iterator; -import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -35,24 +35,22 @@ import org.springframework.util.StringUtils; * will be searched when resolving a given property with a {@link PropertyResolver}. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 * @see PropertySourcesPropertyResolver */ public class MutablePropertySources implements PropertySources { - static final String NON_EXISTENT_PROPERTY_SOURCE_MESSAGE = "PropertySource named [%s] does not exist"; - static final String ILLEGAL_RELATIVE_ADDITION_MESSAGE = "PropertySource named [%s] cannot be added relative to itself"; - private final Log logger; - private final LinkedList<PropertySource<?>> propertySourceList = new LinkedList<PropertySource<?>>(); + private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>(); /** * Create a new {@link MutablePropertySources} object. */ public MutablePropertySources() { - this.logger = LogFactory.getLog(this.getClass()); + this.logger = LogFactory.getLog(getClass()); } /** @@ -62,7 +60,7 @@ public class MutablePropertySources implements PropertySources { public MutablePropertySources(PropertySources propertySources) { this(); for (PropertySource<?> propertySource : propertySources) { - this.addLast(propertySource); + addLast(propertySource); } } @@ -75,15 +73,18 @@ public class MutablePropertySources implements PropertySources { } + @Override public boolean contains(String name) { return this.propertySourceList.contains(PropertySource.named(name)); } + @Override public PropertySource<?> get(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); - return index == -1 ? null : this.propertySourceList.get(index); + return (index != -1 ? this.propertySourceList.get(index) : null); } + @Override public Iterator<PropertySource<?>> iterator() { return this.propertySourceList.iterator(); } @@ -97,7 +98,7 @@ public class MutablePropertySources implements PropertySources { propertySource.getName())); } removeIfPresent(propertySource); - this.propertySourceList.addFirst(propertySource); + this.propertySourceList.add(0, propertySource); } /** @@ -109,7 +110,7 @@ public class MutablePropertySources implements PropertySources { propertySource.getName())); } removeIfPresent(propertySource); - this.propertySourceList.addLast(propertySource); + this.propertySourceList.add(propertySource); } /** @@ -158,7 +159,7 @@ public class MutablePropertySources implements PropertySources { logger.debug(String.format("Removing [%s] PropertySource", name)); } int index = this.propertySourceList.indexOf(PropertySource.named(name)); - return index == -1 ? null : this.propertySourceList.remove(index); + return (index != -1 ? this.propertySourceList.remove(index) : null); } /** @@ -187,7 +188,7 @@ public class MutablePropertySources implements PropertySources { @Override public String toString() { String[] names = new String[this.size()]; - for (int i=0; i < size(); i++) { + for (int i = 0; i < size(); i++) { names[i] = this.propertySourceList.get(i).getName(); } return String.format("[%s]", StringUtils.arrayToCommaDelimitedString(names)); @@ -198,17 +199,17 @@ public class MutablePropertySources implements PropertySources { */ protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) { String newPropertySourceName = propertySource.getName(); - Assert.isTrue(!relativePropertySourceName.equals(newPropertySourceName), - String.format(ILLEGAL_RELATIVE_ADDITION_MESSAGE, newPropertySourceName)); + if (relativePropertySourceName.equals(newPropertySourceName)) { + throw new IllegalArgumentException( + String.format("PropertySource named [%s] cannot be added relative to itself", newPropertySourceName)); + } } /** * Remove the given property source if it is present. */ protected void removeIfPresent(PropertySource<?> propertySource) { - if (this.propertySourceList.contains(propertySource)) { - this.propertySourceList.remove(propertySource); - } + this.propertySourceList.remove(propertySource); } /** @@ -227,7 +228,9 @@ public class MutablePropertySources implements PropertySources { */ private int assertPresentAndGetIndex(String name) { int index = this.propertySourceList.indexOf(PropertySource.named(name)); - Assert.isTrue(index >= 0, String.format(NON_EXISTENT_PROPERTY_SOURCE_MESSAGE, name)); + if (index == -1) { + throw new IllegalArgumentException(String.format("PropertySource named [%s] does not exist", name)); + } return index; } diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java index 492d142d..062e25e9 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,18 @@ import java.util.Properties; * {@link Properties#getProperty} and {@link Properties#setProperty}. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public class PropertiesPropertySource extends MapPropertySource { - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public PropertiesPropertySource(String name, Properties source) { super(name, (Map) source); } + protected PropertiesPropertySource(String name, Map<String, Object> source) { + super(name, source); + } + } diff --git a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java index 2f770277..b5826c51 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java +++ b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java @@ -39,6 +39,7 @@ import org.springframework.util.Assert; */ abstract class ReadOnlySystemAttributesMap implements Map<String, String> { + @Override public boolean containsKey(Object key) { return (get(key) != null); } @@ -47,12 +48,14 @@ abstract class ReadOnlySystemAttributesMap implements Map<String, String> { * @param key the name of the system attribute to retrieve * @throws IllegalArgumentException if given key is non-String */ + @Override public String get(Object key) { Assert.isInstanceOf(String.class, key, String.format("Expected key [%s] to be of type String, got %s", key, key.getClass().getName())); return this.getSystemAttribute((String) key); } + @Override public boolean isEmpty() { return false; } @@ -66,38 +69,47 @@ abstract class ReadOnlySystemAttributesMap implements Map<String, String> { // Unsupported + @Override public int size() { throw new UnsupportedOperationException(); } + @Override public String put(String key, String value) { throw new UnsupportedOperationException(); } + @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } + @Override public String remove(Object key) { throw new UnsupportedOperationException(); } + @Override public void clear() { throw new UnsupportedOperationException(); } + @Override public Set<String> keySet() { return Collections.emptySet(); } + @Override public void putAll(Map<? extends String, ? extends String> map) { throw new UnsupportedOperationException(); } + @Override public Collection<String> values() { return Collections.emptySet(); } + @Override public Set<Entry<String, String>> entrySet() { return Collections.emptySet(); } diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java index b249faf0..b7ad3c42 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java +++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,9 +52,9 @@ package org.springframework.core.env; class SimpleCommandLineArgsParser { /** - * Parse the given {@code String} array based on the rules described - * {@linkplain SimpleCommandLineArgsParser above}, returning a - * fully-populated {@link CommandLineArgs} object. + * Parse the given {@code String} array based on the rules described {@linkplain + * SimpleCommandLineArgsParser above}, returning a fully-populated + * {@link CommandLineArgs} object. * @param args command line arguments, typically from a {@code main()} method */ public CommandLineArgs parse(String... args) { @@ -65,13 +65,13 @@ class SimpleCommandLineArgsParser { String optionName; String optionValue = null; if (optionText.contains("=")) { - optionName = optionText.substring(0, optionText.indexOf('=')); - optionValue = optionText.substring(optionText.indexOf('=') + 1, optionText.length()); + optionName = optionText.substring(0, optionText.indexOf("=")); + optionValue = optionText.substring(optionText.indexOf("=")+1, optionText.length()); } else { optionName = optionText; } - if (optionName.length() == 0 || (optionValue != null && optionValue.length() == 0)) { + if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) { throw new IllegalArgumentException("Invalid argument syntax: " + arg); } commandLineArgs.addOptionArg(optionName, optionValue); diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java index d2022317..f1b1f8d7 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,6 +95,14 @@ public class SimpleCommandLinePropertySource extends CommandLinePropertySource<C super(name, new SimpleCommandLineArgsParser().parse(args)); } + /** + * Get the property names for the option arguments. + */ + @Override + public String[] getPropertyNames() { + return source.getOptionNames().toArray(new String[source.getOptionNames().size()]); + } + @Override protected boolean containsOption(String name) { return this.source.containsOption(name); diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java index 91ea1288..cd001020 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java @@ -46,6 +46,7 @@ public abstract class AbstractResource implements Resource { * falling back to whether an InputStream can be opened. * This will cover both directories and content resources. */ + @Override public boolean exists() { // Try file existence: can we find the file in the file system? try { @@ -67,6 +68,7 @@ public abstract class AbstractResource implements Resource { /** * This implementation always returns {@code true}. */ + @Override public boolean isReadable() { return true; } @@ -74,6 +76,7 @@ public abstract class AbstractResource implements Resource { /** * This implementation always returns {@code false}. */ + @Override public boolean isOpen() { return false; } @@ -82,6 +85,7 @@ public abstract class AbstractResource implements Resource { * This implementation throws a FileNotFoundException, assuming * that the resource cannot be resolved to a URL. */ + @Override public URL getURL() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); } @@ -90,6 +94,7 @@ public abstract class AbstractResource implements Resource { * This implementation builds a URI based on the URL returned * by {@link #getURL()}. */ + @Override public URI getURI() throws IOException { URL url = getURL(); try { @@ -104,6 +109,7 @@ public abstract class AbstractResource implements Resource { * This implementation throws a FileNotFoundException, assuming * that the resource cannot be resolved to an absolute file path. */ + @Override public File getFile() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); } @@ -115,6 +121,7 @@ public abstract class AbstractResource implements Resource { * @see #getInputStream() * @throws IllegalStateException if {@link #getInputStream()} returns null. */ + @Override public long contentLength() throws IOException { InputStream is = this.getInputStream(); Assert.state(is != null, "resource input stream must not be null"); @@ -141,6 +148,7 @@ public abstract class AbstractResource implements Resource { * if available. * @see #getFileForLastModifiedCheck() */ + @Override public long lastModified() throws IOException { long lastModified = getFileForLastModifiedCheck().lastModified(); if (lastModified == 0L) { @@ -165,6 +173,7 @@ public abstract class AbstractResource implements Resource { * This implementation throws a FileNotFoundException, assuming * that relative resources cannot be created for this resource. */ + @Override public Resource createRelative(String relativePath) throws IOException { throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); } @@ -173,6 +182,7 @@ public abstract class AbstractResource implements Resource { * This implementation always returns {@code null}, * assuming that this resource type does not have a filename. */ + @Override public String getFilename() { return null; } diff --git a/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java index d195def9..b9709e38 100644 --- a/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java @@ -93,6 +93,7 @@ public class ByteArrayResource extends AbstractResource { * underlying byte array. * @see java.io.ByteArrayInputStream */ + @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(this.byteArray); } @@ -100,6 +101,7 @@ public class ByteArrayResource extends AbstractResource { /** * This implementation returns the passed-in description, if any. */ + @Override public String getDescription() { return this.description; } diff --git a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java index b1d2998e..dacfb011 100644 --- a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java @@ -156,6 +156,7 @@ public class ClassPathResource extends AbstractFileResolvingResource { * @see java.lang.ClassLoader#getResourceAsStream(String) * @see java.lang.Class#getResourceAsStream(String) */ + @Override public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { @@ -212,6 +213,7 @@ public class ClassPathResource extends AbstractFileResolvingResource { /** * This implementation returns a description that includes the class path location. */ + @Override public String getDescription() { StringBuilder builder = new StringBuilder("class path resource ["); String pathToUse = path; diff --git a/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java index 00f267e4..234c073b 100644 --- a/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java @@ -30,19 +30,20 @@ import org.springframework.util.StringUtils; */ public class ClassRelativeResourceLoader extends DefaultResourceLoader { - private final Class clazz; + private final Class<?> clazz; /** * Create a new ClassRelativeResourceLoader for the given class. * @param clazz the class to load resources through */ - public ClassRelativeResourceLoader(Class clazz) { + public ClassRelativeResourceLoader(Class<?> clazz) { Assert.notNull(clazz, "Class must not be null"); this.clazz = clazz; setClassLoader(clazz.getClassLoader()); } + @Override protected Resource getResourceByPath(String path) { return new ClassRelativeContextResource(path, this.clazz); } @@ -54,13 +55,14 @@ public class ClassRelativeResourceLoader extends DefaultResourceLoader { */ private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource { - private final Class clazz; + private final Class<?> clazz; - public ClassRelativeContextResource(String path, Class clazz) { + public ClassRelativeContextResource(String path, Class<?> clazz) { super(path, clazz); this.clazz = clazz; } + @Override public String getPathWithinContext() { return getPath(); } diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java index 1a390f04..2f22b14c 100644 --- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,14 +79,19 @@ public class DefaultResourceLoader implements ResourceLoader { * ClassPathResource objects created by this resource loader. * @see ClassPathResource */ + @Override public ClassLoader getClassLoader() { return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader()); } + @Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); - if (location.startsWith(CLASSPATH_URL_PREFIX)) { + if (location.startsWith("/")) { + return getResourceByPath(location); + } + else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { @@ -122,12 +127,13 @@ public class DefaultResourceLoader implements ResourceLoader { * ClassPathResource that explicitly expresses a context-relative path * through implementing the ContextResource interface. */ - private static class ClassPathContextResource extends ClassPathResource implements ContextResource { + protected static class ClassPathContextResource extends ClassPathResource implements ContextResource { public ClassPathContextResource(String path, ClassLoader classLoader) { super(path, classLoader); } + @Override public String getPathWithinContext() { return getPath(); } diff --git a/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java b/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java index 8385891e..72021479 100644 --- a/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java @@ -54,11 +54,13 @@ public class DescriptiveResource extends AbstractResource { return false; } + @Override public InputStream getInputStream() throws IOException { throw new FileNotFoundException( getDescription() + " cannot be opened because it does not point to a readable resource"); } + @Override public String getDescription() { return this.description; } diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java index 05202a3b..8f332385 100644 --- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java @@ -110,6 +110,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso * This implementation opens a FileInputStream for the underlying file. * @see java.io.FileInputStream */ + @Override public InputStream getInputStream() throws IOException { return new FileInputStream(this.file); } @@ -173,6 +174,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso * path of the file. * @see java.io.File#getAbsolutePath() */ + @Override public String getDescription() { return "file [" + this.file.getAbsolutePath() + "]"; } @@ -186,6 +188,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso * @see java.io.File#canWrite() * @see java.io.File#isDirectory() */ + @Override public boolean isWritable() { return (this.file.canWrite() && !this.file.isDirectory()); } @@ -194,6 +197,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso * This implementation opens a FileOutputStream for the underlying file. * @see java.io.FileOutputStream */ + @Override public OutputStream getOutputStream() throws IOException { return new FileOutputStream(this.file); } diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java index d811d91f..7446b71f 100644 --- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,7 @@ public class FileSystemResourceLoader extends DefaultResourceLoader { super(path); } + @Override public String getPathWithinContext() { return getPath(); } diff --git a/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java b/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java index d9b2405c..3a37cac9 100644 --- a/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java @@ -88,6 +88,7 @@ public class InputStreamResource extends AbstractResource { * This implementation throws IllegalStateException if attempting to * read the underlying stream multiple times. */ + @Override public InputStream getInputStream() throws IOException, IllegalStateException { if (this.read) { throw new IllegalStateException("InputStream has already been read - " + @@ -100,6 +101,7 @@ public class InputStreamResource extends AbstractResource { /** * This implementation returns the passed-in description, if any. */ + @Override public String getDescription() { return this.description; } diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java new file mode 100644 index 00000000..5a2c9f93 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java @@ -0,0 +1,252 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core.io; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.springframework.util.Assert; + +/** + * {@link Resource} implementation for {@code java.nio.file.Path} handles. + * Supports resolution as File, and also as URL. + * Implements the extended {@link WritableResource} interface. + * + * @author Philippe Marschall + * @since 4.0 + * @see java.nio.file.Path + */ +public class PathResource extends AbstractResource implements WritableResource { + + private final Path path; + + + /** + * Create a new PathResource from a Path handle. + * <p>Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built <i>underneath</i> the + * given root: + * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! + * @param path a Path handle + */ + public PathResource(Path path) { + Assert.notNull(path, "Path must not be null"); + this.path = path.normalize(); + } + + /** + * Create a new PathResource from a Path handle. + * <p>Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built <i>underneath</i> the + * given root: + * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! + * @param path a path + * @see java.nio.file.Paths#get(String, String...) + */ + public PathResource(String path) { + Assert.notNull(path, "Path must not be null"); + this.path = Paths.get(path).normalize(); + } + + /** + * Create a new PathResource from a Path handle. + * <p>Note: Unlike {@link FileSystemResource}, when building relative resources + * via {@link #createRelative}, the relative path will be built <i>underneath</i> the + * given root: + * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"! + * @see java.nio.file.Paths#get(URI) + * @param uri a path URI + */ + public PathResource(URI uri) { + Assert.notNull(uri, "URI must not be null"); + this.path = Paths.get(uri).normalize(); + } + + + /** + * Return the file path for this resource. + */ + public final String getPath() { + return this.path.toString(); + } + + /** + * This implementation returns whether the underlying file exists. + * @see org.springframework.core.io.PathResource#exists() + */ + @Override + public boolean exists() { + return Files.exists(this.path); + } + + /** + * This implementation checks whether the underlying file is marked as readable + * (and corresponds to an actual file with content, not to a directory). + * @see java.nio.file.Files#isReadable(Path) + * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...) + */ + @Override + public boolean isReadable() { + return (Files.isReadable(this.path) && !Files.isDirectory(this.path)); + } + + /** + * This implementation opens a InputStream for the underlying file. + * @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...) + */ + @Override + public InputStream getInputStream() throws IOException { + if(!exists()) { + throw new FileNotFoundException(getPath() + " (No such file or directory)"); + } + if(Files.isDirectory(this.path)) { + throw new FileNotFoundException(getPath() + " (Is a directory)"); + } + return Files.newInputStream(this.path); + } + + /** + * This implementation returns a URL for the underlying file. + * @see java.nio.file.Path#toUri() + * @see java.net.URI#toURL() + */ + @Override + public URL getURL() throws IOException { + return this.path.toUri().toURL(); + } + + /** + * This implementation returns a URI for the underlying file. + * @see java.nio.file.Path#toUri() + */ + @Override + public URI getURI() throws IOException { + return this.path.toUri(); + } + + /** + * This implementation returns the underlying File reference. + */ + @Override + public File getFile() throws IOException { + try { + return this.path.toFile(); + } + catch (UnsupportedOperationException ex) { + // only Paths on the default file system can be converted to a File + // do exception translation for cases where conversion is not possible + throw new FileNotFoundException(this.path + " cannot be resolved to " + + "absolute file path"); + } + } + + /** + * This implementation returns the underlying File's length. + */ + @Override + public long contentLength() throws IOException { + return Files.size(this.path); + } + + /** + * This implementation returns the underlying File's timestamp. + * @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...) + */ + @Override + public long lastModified() throws IOException { + // we can not use the super class method since it uses conversion to a File and + // only Paths on the default file system can be converted to a File + return Files.getLastModifiedTime(path).toMillis(); + } + + /** + * This implementation creates a FileResource, applying the given path + * relative to the path of the underlying file of this resource descriptor. + * @see java.nio.file.Path#resolve(String) + */ + @Override + public Resource createRelative(String relativePath) throws IOException { + return new PathResource(this.path.resolve(relativePath)); + } + + /** + * This implementation returns the name of the file. + * @see java.nio.file.Path#getFileName() + */ + @Override + public String getFilename() { + return this.path.getFileName().toString(); + } + + @Override + public String getDescription() { + return "path [" + this.path.toAbsolutePath() + "]"; + } + + // implementation of WritableResource + + /** + * This implementation checks whether the underlying file is marked as writable + * (and corresponds to an actual file with content, not to a directory). + * @see java.nio.file.Files#isWritable(Path) + * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...) + */ + @Override + public boolean isWritable() { + return Files.isWritable(this.path) && !Files.isDirectory(this.path); + } + + /** + * This implementation opens a OutputStream for the underlying file. + * @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...) + */ + @Override + public OutputStream getOutputStream() throws IOException { + if(Files.isDirectory(this.path)) { + throw new FileNotFoundException(getPath() + " (Is a directory)"); + } + return Files.newOutputStream(this.path); + } + + + /** + * This implementation compares the underlying Path references. + */ + @Override + public boolean equals(Object obj) { + return (obj == this || + (obj instanceof PathResource && this.path.equals(((PathResource) obj).path))); + } + + /** + * This implementation returns the hash code of the underlying Path reference. + */ + @Override + public int hashCode() { + return this.path.hashCode(); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java index cc6eef30..e3fc2a4e 100644 --- a/spring-core/src/main/java/org/springframework/core/io/Resource.java +++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java @@ -42,6 +42,7 @@ import java.net.URL; * @see UrlResource * @see ByteArrayResource * @see InputStreamResource + * @see PathResource */ public interface Resource extends InputStreamSource { diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java index 626f8f70..77f8b8bf 100644 --- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java @@ -160,6 +160,7 @@ public class UrlResource extends AbstractFileResolvingResource { * @see java.net.URLConnection#setUseCaches(boolean) * @see java.net.URLConnection#getInputStream() */ + @Override public InputStream getInputStream() throws IOException { URLConnection con = this.url.openConnection(); ResourceUtils.useCachesIfNecessary(con); @@ -238,6 +239,7 @@ public class UrlResource extends AbstractFileResolvingResource { /** * This implementation returns a description that includes the URL. */ + @Override public String getDescription() { return "URL [" + this.url + "]"; } diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java index 4877d0ad..a0ab31fe 100644 --- a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,11 @@ import org.springframework.core.NestedIOException; import org.springframework.util.Assert; /** - * VFS based {@link Resource} implementation. - * Supports the corresponding VFS API versions on JBoss AS 5.x as well as 6.x and 7.x. + * JBoss VFS based {@link Resource} implementation. + * + * <p>As of Spring 4.0, this class supports VFS 3.x on JBoss AS 6+ (package + * {@code org.jboss.vfs}) and is in particular compatible with JBoss AS 7 and + * WildFly 8. * * @author Ales Justin * @author Juergen Hoeller @@ -40,12 +43,13 @@ public class VfsResource extends AbstractResource { private final Object resource; - public VfsResource(Object resources) { - Assert.notNull(resources, "VirtualFile must not be null"); - this.resource = resources; + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.resource = resource; } + @Override public InputStream getInputStream() throws IOException { return VfsUtils.getInputStream(this.resource); } @@ -114,6 +118,7 @@ public class VfsResource extends AbstractResource { return VfsUtils.getName(this.resource); } + @Override public String getDescription() { return this.resource.toString(); } diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java b/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java index 8c9a7aa1..9291cee6 100644 --- a/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,36 +25,27 @@ import java.lang.reflect.Method; import java.net.URI; import java.net.URL; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.NestedIOException; import org.springframework.util.ReflectionUtils; /** - * Utility for detecting the JBoss VFS version available in the classpath. - * JBoss AS 5+ uses VFS 2.x (package {@code org.jboss.virtual}) while - * JBoss AS 6+ uses VFS 3.x (package {@code org.jboss.vfs}). + * Utility for detecting and accessing JBoss VFS in the classpath. * - * <p>Thanks go to Marius Bogoevici for the initial patch. + * <p>As of Spring 4.0, this class supports VFS 3.x on JBoss AS 6+ (package + * {@code org.jboss.vfs}) and is in particular compatible with JBoss AS 7 and + * WildFly 8. * + * <p>Thanks go to Marius Bogoevici for the initial patch. * <b>Note:</b> This is an internal class and should not be used outside the framework. * * @author Costin Leau + * @author Juergen Hoeller * @since 3.0.3 */ public abstract class VfsUtils { - private static final Log logger = LogFactory.getLog(VfsUtils.class); - - private static final String VFS2_PKG = "org.jboss.virtual."; private static final String VFS3_PKG = "org.jboss.vfs."; private static final String VFS_NAME = "VFS"; - private static enum VFS_VER { V2, V3 } - - private static VFS_VER version; - private static Method VFS_METHOD_GET_ROOT_URL = null; private static Method VFS_METHOD_GET_ROOT_URI = null; @@ -71,54 +62,17 @@ public abstract class VfsUtils { protected static Class<?> VIRTUAL_FILE_VISITOR_INTERFACE; protected static Method VIRTUAL_FILE_METHOD_VISIT; - private static Method VFS_UTILS_METHOD_IS_NESTED_FILE = null; - private static Method VFS_UTILS_METHOD_GET_COMPATIBLE_URI = null; private static Field VISITOR_ATTRIBUTES_FIELD_RECURSE = null; private static Method GET_PHYSICAL_FILE = null; static { ClassLoader loader = VfsUtils.class.getClassLoader(); - String pkg; - Class<?> vfsClass; - - // check for JBoss 6 - try { - vfsClass = loader.loadClass(VFS3_PKG + VFS_NAME); - version = VFS_VER.V3; - pkg = VFS3_PKG; - - if (logger.isDebugEnabled()) { - logger.debug("JBoss VFS packages for JBoss AS 6 found"); - } - } - catch (ClassNotFoundException ex) { - // fallback to JBoss 5 - if (logger.isDebugEnabled()) - logger.debug("JBoss VFS packages for JBoss AS 6 not found; falling back to JBoss AS 5 packages"); - try { - vfsClass = loader.loadClass(VFS2_PKG + VFS_NAME); - - version = VFS_VER.V2; - pkg = VFS2_PKG; - - if (logger.isDebugEnabled()) - logger.debug("JBoss VFS packages for JBoss AS 5 found"); - } - catch (ClassNotFoundException ex2) { - logger.error("JBoss VFS packages (for both JBoss AS 5 and 6) were not found - JBoss VFS support disabled"); - throw new IllegalStateException("Cannot detect JBoss VFS packages", ex2); - } - } - - // cache reflective information try { - String methodName = (VFS_VER.V3.equals(version) ? "getChild" : "getRoot"); - - VFS_METHOD_GET_ROOT_URL = ReflectionUtils.findMethod(vfsClass, methodName, URL.class); - VFS_METHOD_GET_ROOT_URI = ReflectionUtils.findMethod(vfsClass, methodName, URI.class); - - Class<?> virtualFile = loader.loadClass(pkg + "VirtualFile"); + Class<?> vfsClass = loader.loadClass(VFS3_PKG + VFS_NAME); + VFS_METHOD_GET_ROOT_URL = ReflectionUtils.findMethod(vfsClass, "getChild", URL.class); + VFS_METHOD_GET_ROOT_URI = ReflectionUtils.findMethod(vfsClass, "getChild", URI.class); + Class<?> virtualFile = loader.loadClass(VFS3_PKG + "VirtualFile"); VIRTUAL_FILE_METHOD_EXISTS = ReflectionUtils.findMethod(virtualFile, "exists"); VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = ReflectionUtils.findMethod(virtualFile, "openStream"); VIRTUAL_FILE_METHOD_GET_SIZE = ReflectionUtils.findMethod(virtualFile, "getSize"); @@ -128,25 +82,16 @@ public abstract class VfsUtils { VIRTUAL_FILE_METHOD_GET_NAME = ReflectionUtils.findMethod(virtualFile, "getName"); VIRTUAL_FILE_METHOD_GET_PATH_NAME = ReflectionUtils.findMethod(virtualFile, "getPathName"); GET_PHYSICAL_FILE = ReflectionUtils.findMethod(virtualFile, "getPhysicalFile"); + VIRTUAL_FILE_METHOD_GET_CHILD = ReflectionUtils.findMethod(virtualFile, "getChild", String.class); - methodName = (VFS_VER.V3.equals(version) ? "getChild" : "findChild"); - - VIRTUAL_FILE_METHOD_GET_CHILD = ReflectionUtils.findMethod(virtualFile, methodName, String.class); - - Class<?> utilsClass = loader.loadClass(pkg + "VFSUtils"); - - VFS_UTILS_METHOD_GET_COMPATIBLE_URI = ReflectionUtils.findMethod(utilsClass, "getCompatibleURI", - virtualFile); - VFS_UTILS_METHOD_IS_NESTED_FILE = ReflectionUtils.findMethod(utilsClass, "isNestedFile", virtualFile); - - VIRTUAL_FILE_VISITOR_INTERFACE = loader.loadClass(pkg + "VirtualFileVisitor"); + VIRTUAL_FILE_VISITOR_INTERFACE = loader.loadClass(VFS3_PKG + "VirtualFileVisitor"); VIRTUAL_FILE_METHOD_VISIT = ReflectionUtils.findMethod(virtualFile, "visit", VIRTUAL_FILE_VISITOR_INTERFACE); - Class<?> visitorAttributesClass = loader.loadClass(pkg + "VisitorAttributes"); + Class<?> visitorAttributesClass = loader.loadClass(VFS3_PKG + "VisitorAttributes"); VISITOR_ATTRIBUTES_FIELD_RECURSE = ReflectionUtils.findField(visitorAttributesClass, "RECURSE"); } catch (ClassNotFoundException ex) { - throw new IllegalStateException("Could not detect the JBoss VFS infrastructure", ex); + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); } } @@ -224,20 +169,7 @@ public abstract class VfsUtils { } static File getFile(Object vfsResource) throws IOException { - if (VFS_VER.V2.equals(version)) { - if ((Boolean) invokeVfsMethod(VFS_UTILS_METHOD_IS_NESTED_FILE, null, vfsResource)) { - throw new IOException("File resolution not supported for nested resource: " + vfsResource); - } - try { - return new File((URI) invokeVfsMethod(VFS_UTILS_METHOD_GET_COMPATIBLE_URI, null, vfsResource)); - } - catch (Exception ex) { - throw new NestedIOException("Failed to obtain File reference for " + vfsResource, ex); - } - } - else { - return (File) invokeVfsMethod(GET_PHYSICAL_FILE, vfsResource); - } + return (File) invokeVfsMethod(GET_PHYSICAL_FILE, vfsResource); } static Object getRoot(URI url) throws IOException { @@ -257,4 +189,5 @@ public abstract class VfsUtils { protected static String doGetPath(Object resource) { return (String) ReflectionUtils.invokeMethod(VIRTUAL_FILE_METHOD_GET_PATH_NAME, resource); } + } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java index 04a17383..59643dd6 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import org.springframework.util.ObjectUtils; * with a specific encoding to be used for reading from the resource. * * <p>Used as argument for operations that support to read content with - * a specific encoding (usually through a {@code java.io.Reader}. + * a specific encoding (usually through a {@code java.io.Reader}). * * @author Juergen Hoeller * @since 1.2.6 diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 72b0c481..1f54f638 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -233,6 +233,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol return this.resourceLoader; } + @Override public ClassLoader getClassLoader() { return getResourceLoader().getClassLoader(); } @@ -255,10 +256,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } + @Override public Resource getResource(String location) { return getResourceLoader().getResource(location); } + @Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { @@ -678,6 +681,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol this.rootPath = (rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/"); } + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (Object.class.equals(method.getDeclaringClass())) { @@ -722,6 +726,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol return this.resources.size(); } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("sub-pattern: ").append(this.subPattern); diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java index d0795545..cec785b3 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.core.io.support; import java.io.IOException; +import java.util.Map; import java.util.Properties; import org.springframework.core.env.PropertiesPropertySource; @@ -36,6 +37,8 @@ import org.springframework.util.StringUtils; * @author Chris Beams * @author Juergen Hoeller * @since 3.1 + * @see org.springframework.core.io.Resource + * @see org.springframework.core.io.support.EncodedResource */ public class ResourcePropertySource extends PropertiesPropertySource { @@ -112,10 +115,27 @@ public class ResourcePropertySource extends PropertiesPropertySource { this(new DefaultResourceLoader().getResource(location)); } + private ResourcePropertySource(String name, Map<String, Object> source) { + super(name, source); + } + + + /** + * Return a potentially adapted variant of this {@link ResourcePropertySource}, + * overriding the previously given (or derived) name with the specified name. + */ + public ResourcePropertySource withName(String name) { + if (this.name.equals(name)) { + return this; + } + return new ResourcePropertySource(name, this.source); + } + /** - * Return the description string for the resource, and if empty returns - * the class name of the resource plus its identity hash code. + * Return the description String for the given Resource; it the description is + * empty, return the class name of the resource plus its identity hash code. + * @see org.springframework.core.io.Resource#getDescription() */ private static String getNameForResource(Resource resource) { String name = resource.getDescription(); diff --git a/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java index f6e523ea..113970dc 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java @@ -45,8 +45,9 @@ abstract class VfsPatternUtils extends VfsUtils { } static void visit(Object resource, InvocationHandler visitor) throws IOException { - Object visitorProxy = Proxy.newProxyInstance(VIRTUAL_FILE_VISITOR_INTERFACE.getClassLoader(), - new Class<?>[] { VIRTUAL_FILE_VISITOR_INTERFACE }, visitor); + Object visitorProxy = Proxy.newProxyInstance( + VIRTUAL_FILE_VISITOR_INTERFACE.getClassLoader(), + new Class<?>[] {VIRTUAL_FILE_VISITOR_INTERFACE}, visitor); invokeVfsMethod(VIRTUAL_FILE_METHOD_VISIT, resource, visitorProxy); } diff --git a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java index f61f697d..66a91b38 100644 --- a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java +++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java @@ -34,6 +34,7 @@ public class DefaultDeserializer implements Deserializer<Object> { /** * Reads the input stream and deserializes into an object. */ + @Override public Object deserialize(InputStream inputStream) throws IOException { ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); try { diff --git a/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java index 04fe8bed..6146ee01 100644 --- a/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java +++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java @@ -34,6 +34,7 @@ public class DefaultSerializer implements Serializer<Object> { * Writes the source object to an output stream using Java Serialization. * The source object must implement {@link Serializable}. */ + @Override public void serialize(Object object, OutputStream outputStream) throws IOException { if (!(object instanceof Serializable)) { throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " + diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java b/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java index 20020611..074acf7b 100644 --- a/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java +++ b/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,7 @@ public class DeserializingConverter implements Converter<byte[], Object> { } + @Override public Object convert(byte[] source) { ByteArrayInputStream byteStream = new ByteArrayInputStream(source); try { diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java index b9909f61..1a35c6a2 100644 --- a/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java +++ b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java @@ -55,6 +55,7 @@ public class SerializingConverter implements Converter<Object, byte[]> { /** * Serializes the source object and returns the byte array result. */ + @Override public byte[] convert(Object source) { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(256); try { diff --git a/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java b/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java index f81cb4a8..97a6ce23 100644 --- a/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java +++ b/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java @@ -52,6 +52,7 @@ public class DefaultToStringStyler implements ToStringStyler { } + @Override public void styleStart(StringBuilder buffer, Object obj) { if (!obj.getClass().isArray()) { buffer.append('[').append(ClassUtils.getShortName(obj.getClass())); @@ -70,10 +71,12 @@ public class DefaultToStringStyler implements ToStringStyler { buffer.append(ObjectUtils.getIdentityHexString(obj)); } + @Override public void styleEnd(StringBuilder buffer, Object o) { buffer.append(']'); } + @Override public void styleField(StringBuilder buffer, String fieldName, Object value) { styleFieldStart(buffer, fieldName); styleValue(buffer, value); @@ -87,10 +90,12 @@ public class DefaultToStringStyler implements ToStringStyler { protected void styleFieldEnd(StringBuilder buffer, String fieldName) { } + @Override public void styleValue(StringBuilder buffer, Object value) { buffer.append(this.valueStyler.style(value)); } + @Override public void styleFieldSeparator(StringBuilder buffer) { buffer.append(','); } diff --git a/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java b/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java index 2e6251c0..e32986f6 100644 --- a/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java +++ b/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java @@ -48,6 +48,7 @@ public class DefaultValueStyler implements ValueStyler { private static final String ARRAY = "array"; + @Override public String style(Object value) { if (value == null) { return NULL; @@ -56,20 +57,20 @@ public class DefaultValueStyler implements ValueStyler { return "\'" + value + "\'"; } else if (value instanceof Class) { - return ClassUtils.getShortName((Class) value); + return ClassUtils.getShortName((Class<?>) value); } else if (value instanceof Method) { Method method = (Method) value; return method.getName() + "@" + ClassUtils.getShortName(method.getDeclaringClass()); } else if (value instanceof Map) { - return style((Map) value); + return style((Map<?, ?>) value); } else if (value instanceof Map.Entry) { - return style((Map.Entry) value); + return style((Map.Entry<? ,?>) value); } else if (value instanceof Collection) { - return style((Collection) value); + return style((Collection<?>) value); } else if (value.getClass().isArray()) { return styleArray(ObjectUtils.toObjectArray(value)); @@ -79,11 +80,11 @@ public class DefaultValueStyler implements ValueStyler { } } - private String style(Map value) { + private <K, V> String style(Map<K, V> value) { StringBuilder result = new StringBuilder(value.size() * 8 + 16); result.append(MAP + "["); - for (Iterator it = value.entrySet().iterator(); it.hasNext();) { - Map.Entry entry = (Map.Entry) it.next(); + for (Iterator<Map.Entry<K, V>> it = value.entrySet().iterator(); it.hasNext();) { + Map.Entry<K, V> entry = it.next(); result.append(style(entry)); if (it.hasNext()) { result.append(',').append(' '); @@ -96,14 +97,14 @@ public class DefaultValueStyler implements ValueStyler { return result.toString(); } - private String style(Map.Entry value) { + private String style(Map.Entry<?, ?> value) { return style(value.getKey()) + " -> " + style(value.getValue()); } - private String style(Collection value) { + private String style(Collection<?> value) { StringBuilder result = new StringBuilder(value.size() * 8 + 16); result.append(getCollectionTypeString(value)).append('['); - for (Iterator i = value.iterator(); i.hasNext();) { + for (Iterator<?> i = value.iterator(); i.hasNext();) { result.append(style(i.next())); if (i.hasNext()) { result.append(',').append(' '); @@ -116,7 +117,7 @@ public class DefaultValueStyler implements ValueStyler { return result.toString(); } - private String getCollectionTypeString(Collection value) { + private String getCollectionTypeString(Collection<?> value) { if (value instanceof List) { return LIST; } diff --git a/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java new file mode 100644 index 00000000..6adaad38 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.core.task; + +import java.util.concurrent.Callable; + +import org.springframework.util.concurrent.ListenableFuture; + +/** + * Extension of the {@link AsyncTaskExecutor} interface, adding the capability to submit + * tasks for {@link ListenableFuture}s. + * + * @author Arjen Poutsma + * @since 4.0 + * @see ListenableFuture + */ +public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor { + + /** + * Submit a {@code Runnable} task for execution, receiving a {@code ListenableFuture} + * representing that task. The Future will return a {@code null} result upon completion. + * @param task the {@code Runnable} to execute (never {@code null}) + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws TaskRejectedException if the given task was not accepted + */ + ListenableFuture<?> submitListenable(Runnable task); + + /** + * Submit a {@code Callable} task for execution, receiving a {@code ListenableFuture} + * representing that task. The Future will return the Callable's result upon + * completion. + * @param task the {@code Callable} to execute (never {@code null}) + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws TaskRejectedException if the given task was not accepted + */ + <T> ListenableFuture<T> submitListenable(Callable<T> task); + +} diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java index f6b71400..05e759ab 100644 --- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java @@ -25,6 +25,8 @@ import java.util.concurrent.ThreadFactory; import org.springframework.util.Assert; import org.springframework.util.ConcurrencyThrottleSupport; import org.springframework.util.CustomizableThreadCreator; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureTask; /** * {@link TaskExecutor} implementation that fires up a new Thread for each task, @@ -45,7 +47,7 @@ import org.springframework.util.CustomizableThreadCreator; * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor */ @SuppressWarnings("serial") -public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncTaskExecutor, Serializable { +public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncListenableTaskExecutor, Serializable { /** * Permit any number of concurrent invocations: that is, don't throttle concurrency. @@ -144,6 +146,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement * if configured (through the superclass's settings). * @see #doExecute(Runnable) */ + @Override public void execute(Runnable task) { execute(task, TIMEOUT_INDEFINITE); } @@ -157,6 +160,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement * @see #TIMEOUT_IMMEDIATE * @see #doExecute(Runnable) */ + @Override public void execute(Runnable task, long startTimeout) { Assert.notNull(task, "Runnable must not be null"); if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) { @@ -168,18 +172,34 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement } } + @Override public Future<?> submit(Runnable task) { FutureTask<Object> future = new FutureTask<Object>(task, null); execute(future, TIMEOUT_INDEFINITE); return future; } + @Override public <T> Future<T> submit(Callable<T> task) { FutureTask<T> future = new FutureTask<T>(task); execute(future, TIMEOUT_INDEFINITE); return future; } + @Override + public ListenableFuture<?> submitListenable(Runnable task) { + ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null); + execute(future, TIMEOUT_INDEFINITE); + return future; + } + + @Override + public <T> ListenableFuture<T> submitListenable(Callable<T> task) { + ListenableFutureTask<T> future = new ListenableFutureTask<T>(task); + execute(future, TIMEOUT_INDEFINITE); + return future; + } + /** * Template method for the actual execution of a task. * <p>The default implementation creates a new Thread and starts it. @@ -225,6 +245,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement this.target = target; } + @Override public void run() { try { this.target.run(); diff --git a/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java index 47bebf46..64f8c793 100644 --- a/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java @@ -44,6 +44,7 @@ public class SyncTaskExecutor implements TaskExecutor, Serializable { * invocation of it's {@link Runnable#run() run()} method. * @throws IllegalArgumentException if the given {@code task} is {@code null} */ + @Override public void execute(Runnable task) { Assert.notNull(task, "Runnable must not be null"); task.run(); diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java index fea960e3..bf8b9f91 100644 --- a/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java @@ -45,6 +45,7 @@ public interface TaskExecutor extends Executor { * @param task the {@code Runnable} to execute (never {@code null}) * @throws TaskRejectedException if the given task was not accepted */ + @Override void execute(Runnable task); } diff --git a/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java index 2acf1a28..8f9f09ed 100644 --- a/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ public class ConcurrentExecutorAdapter implements Executor { } + @Override public void execute(Runnable command) { this.taskExecutor.execute(command); } diff --git a/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java index 0c594b93..393ecee6 100644 --- a/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java @@ -57,29 +57,35 @@ public class ExecutorServiceAdapter extends AbstractExecutorService { } + @Override public void execute(Runnable task) { this.taskExecutor.execute(task); } + @Override public void shutdown() { throw new IllegalStateException( "Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle"); } + @Override public List<Runnable> shutdownNow() { throw new IllegalStateException( "Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle"); } + @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { throw new IllegalStateException( "Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle"); } + @Override public boolean isShutdown() { return false; } + @Override public boolean isTerminated() { return false; } diff --git a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java index f980c4d4..fad9ae09 100644 --- a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java @@ -23,9 +23,11 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; -import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.TaskRejectedException; import org.springframework.util.Assert; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureTask; /** * Adapter that takes a JDK {@code java.util.concurrent.Executor} and @@ -39,7 +41,7 @@ import org.springframework.util.Assert; * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.Executors */ -public class TaskExecutorAdapter implements AsyncTaskExecutor { +public class TaskExecutorAdapter implements AsyncListenableTaskExecutor { private final Executor concurrentExecutor; @@ -59,6 +61,7 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor { * Delegates to the specified JDK concurrent executor. * @see java.util.concurrent.Executor#execute(Runnable) */ + @Override public void execute(Runnable task) { try { this.concurrentExecutor.execute(task); @@ -69,10 +72,12 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor { } } + @Override public void execute(Runnable task, long startTimeout) { execute(task); } + @Override public Future<?> submit(Runnable task) { try { if (this.concurrentExecutor instanceof ExecutorService) { @@ -90,6 +95,7 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor { } } + @Override public <T> Future<T> submit(Callable<T> task) { try { if (this.concurrentExecutor instanceof ExecutorService) { @@ -107,4 +113,30 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor { } } + @Override + public ListenableFuture<?> submitListenable(Runnable task) { + try { + ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null); + this.concurrentExecutor.execute(future); + return future; + } + catch (RejectedExecutionException ex) { + throw new TaskRejectedException( + "Executor [" + this.concurrentExecutor + "] did not accept task: " + task, ex); + } + } + + @Override + public <T> ListenableFuture<T> submitListenable(Callable<T> task) { + try { + ListenableFutureTask<T> future = new ListenableFutureTask<T>(task); + this.concurrentExecutor.execute(future); + return future; + } + catch (RejectedExecutionException ex) { + throw new TaskRejectedException( + "Executor [" + this.concurrentExecutor + "] did not accept task: " + task, ex); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java new file mode 100644 index 00000000..be7e31d2 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core.type; + +import java.util.Map; + +import org.springframework.util.MultiValueMap; + +/** + * Defines access to the annotations of a specific type ({@link AnnotationMetadata class} + * or {@link MethodMetadata method}), in a form that does not necessarily require the + * class-loading. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Mark Pollack + * @author Chris Beams + * @author Phillip Webb + * @author Sam Brannen + * @since 4.0 + * @see AnnotationMetadata + * @see MethodMetadata + */ +public interface AnnotatedTypeMetadata { + + /** + * Determine whether the underlying element has an annotation or meta-annotation + * of the given type defined. + * <p>If this method returns {@code true}, then + * {@link #getAnnotationAttributes} will return a non-null Map. + * @param annotationType the annotation type to look for + * @return whether a matching annotation is defined + */ + boolean isAnnotated(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, if any (i.e. if + * defined on the underlying element, as direct annotation or meta-annotation), + * also taking attribute overrides on composed annotations into account. + * @param annotationType the annotation type to look for + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * {@code null} if no matching annotation is defined. + */ + Map<String, Object> getAnnotationAttributes(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, if any (i.e. if + * defined on the underlying element, as direct annotation or meta-annotation), + * also taking attribute overrides on composed annotations into account. + * @param annotationType the annotation type to look for + * @param classValuesAsString whether to convert class references to String + * class names for exposure as values in the returned Map, instead of Class + * references which might potentially have to be loaded first + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * {@code null} if no matching annotation is defined. + */ + Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString); + + /** + * Retrieve all attributes of all annotations of the given type, if any (i.e. if + * defined on the underlying element, as direct annotation or meta-annotation). + * Note that this variant does <i>not</i> take attribute overrides into account. + * @param annotationType the annotation type to look for + * @return a MultiMap of attributes, with the attribute name as key (e.g. "value") + * and a list of the defined attribute values as Map value. This return value will + * be {@code null} if no matching annotation is defined. + * @see #getAllAnnotationAttributes(String, boolean) + */ + MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType); + + /** + * Retrieve all attributes of all annotations of the given type, if any (i.e. if + * defined on the underlying element, as direct annotation or meta-annotation). + * Note that this variant does <i>not</i> take attribute overrides into account. + * @param annotationType the annotation type to look for + * @param classValuesAsString whether to convert class references to String + * @return a MultiMap of attributes, with the attribute name as key (e.g. "value") + * and a list of the defined attribute values as Map value. This return value will + * be {@code null} if no matching annotation is defined. + * @see #getAllAnnotationAttributes(String) + */ + MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString); + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java index beeb7d93..8c2ef4ca 100644 --- a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.core.type; -import java.util.Map; import java.util.Set; /** @@ -25,79 +24,47 @@ import java.util.Set; * * @author Juergen Hoeller * @author Mark Fisher + * @author Phillip Webb + * @author Sam Brannen * @since 2.5 * @see StandardAnnotationMetadata * @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata() + * @see AnnotatedTypeMetadata */ -public interface AnnotationMetadata extends ClassMetadata { +public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { /** - * Return the names of all annotation types defined on the underlying class. + * Return the names of all annotation types that are <em>present</em> on the + * underlying class. * @return the annotation type names */ Set<String> getAnnotationTypes(); /** - * Return the names of all meta-annotation types defined on the - * given annotation type of the underlying class. + * Return the names of all meta-annotation types <em>present</em> on the + * given annotation type on the underlying class. * @param annotationType the meta-annotation type to look for * @return the meta-annotation type names */ Set<String> getMetaAnnotationTypes(String annotationType); /** - * Determine whether the underlying class has an annotation of the given - * type defined. + * Determine whether an annotation of the given type is <em>present</em> on + * the underlying class. * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined + * @return whether a matching annotation is present */ boolean hasAnnotation(String annotationType); /** - * Determine whether the underlying class has an annotation that - * is itself annotated with the meta-annotation of the given type. + * Determine whether the underlying class has an annotation that is itself + * annotated with the meta-annotation of the given type. * @param metaAnnotationType the meta-annotation type to look for - * @return whether a matching meta-annotation is defined + * @return whether a matching meta-annotation is present */ boolean hasMetaAnnotation(String metaAnnotationType); /** - * Determine whether the underlying class has an annotation or - * meta-annotation of the given type defined. - * <p>This is equivalent to a "hasAnnotation || hasMetaAnnotation" - * check. If this method returns {@code true}, then - * {@link #getAnnotationAttributes} will return a non-null Map. - * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined - */ - boolean isAnnotated(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map<String, Object> getAnnotationAttributes(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @param classValuesAsString whether to convert class references to String - * class names for exposure as values in the returned Map, instead of Class - * references which might potentially have to be loaded first - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString); - - /** * Determine whether the underlying class has any methods that are * annotated (or meta-annotated) with the given annotation type. */ @@ -109,7 +76,7 @@ public interface AnnotationMetadata extends ClassMetadata { * <p>For any returned method, {@link MethodMetadata#isAnnotated} will * return {@code true} for the given annotation type. * @param annotationType the annotation type to look for - * @return a Set of {@link MethodMetadata} for methods that have a matching + * @return a set of {@link MethodMetadata} for methods that have a matching * annotation. The return value will be an empty set if no methods match * the annotation type. */ diff --git a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java index 17d7bab2..585965ca 100644 --- a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.core.type; -import java.util.Map; - /** * Interface that defines abstract access to the annotations of a specific * class, in a form that does not require that class to be loaded yet. @@ -25,11 +23,13 @@ import java.util.Map; * @author Juergen Hoeller * @author Mark Pollack * @author Chris Beams + * @author Phillip Webb * @since 3.0 * @see StandardMethodMetadata * @see AnnotationMetadata#getAnnotatedMethods + * @see AnnotatedTypeMetadata */ -public interface MethodMetadata { +public interface MethodMetadata extends AnnotatedTypeMetadata { /** * Return the name of the method. @@ -57,23 +57,4 @@ public interface MethodMetadata { */ boolean isOverridable(); - /** - * Determine whether the underlying method has an annotation or - * meta-annotation of the given type defined. - * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined - */ - boolean isAnnotated(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying method, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map<String, Object> getAnnotationAttributes(String annotationType); - } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 1a4e5365..cc2a86ce 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -22,8 +22,8 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.MultiValueMap; /** * {@link AnnotationMetadata} implementation that uses standard reflection @@ -32,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils; * @author Juergen Hoeller * @author Mark Fisher * @author Chris Beams + * @author Phillip Webb * @since 2.5 */ public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { @@ -51,11 +52,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements /** * Create a new {@link StandardAnnotationMetadata} wrapper for the given Class, * providing the option to return any nested annotations or annotation arrays in the - * form of {@link AnnotationAttributes} instead of actual {@link Annotation} instances. - * @param introspectedClass the Class to instrospect + * form of {@link org.springframework.core.annotation.AnnotationAttributes} instead + * of actual {@link Annotation} instances. + * @param introspectedClass the Class to introspect * @param nestedAnnotationsAsMap return nested annotations and annotation arrays as - * {@link AnnotationAttributes} for compatibility with ASM-based - * {@link AnnotationMetadata} implementations + * {@link org.springframework.core.annotation.AnnotationAttributes} for compatibility + * with ASM-based {@link AnnotationMetadata} implementations * @since 3.1.1 */ public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) { @@ -64,6 +66,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } + @Override public Set<String> getAnnotationTypes() { Set<String> types = new LinkedHashSet<String>(); Annotation[] anns = getIntrospectedClass().getAnnotations(); @@ -73,24 +76,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements return types; } + @Override public Set<String> getMetaAnnotationTypes(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - Set<String> types = new LinkedHashSet<String>(); - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation metaAnn : metaAnns) { - types.add(metaAnn.annotationType().getName()); - for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { - types.add(metaMetaAnn.annotationType().getName()); - } - } - return types; - } - } - return null; + return AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationType); } + @Override public boolean hasAnnotation(String annotationType) { Annotation[] anns = getIntrospectedClass().getAnnotations(); for (Annotation ann : anns) { @@ -101,102 +92,56 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements return false; } + @Override public boolean hasMetaAnnotation(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation metaAnn : metaAnns) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { - if (metaMetaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - } - return false; + return AnnotatedElementUtils.hasMetaAnnotationTypes(getIntrospectedClass(), annotationType); } + @Override public boolean isAnnotated(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - return false; + return AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationType); } + @Override public Map<String, Object> getAnnotationAttributes(String annotationType) { return this.getAnnotationAttributes(annotationType, false); } + @Override public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - ann, classValuesAsString, this.nestedAnnotationsAsMap); - } - } - for (Annotation ann : anns) { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - metaAnn, classValuesAsString, this.nestedAnnotationsAsMap); - } - } - } - return null; + return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(), + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); + } + + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(), + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); } + @Override public boolean hasAnnotatedMethods(String annotationType) { Method[] methods = getIntrospectedClass().getDeclaredMethods(); for (Method method : methods) { - if (!method.isBridge()) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - } + if (!method.isBridge() && AnnotatedElementUtils.isAnnotated(method, annotationType)) { + return true; } } return false; } + @Override public Set<MethodMetadata> getAnnotatedMethods(String annotationType) { - Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>(); Method[] methods = getIntrospectedClass().getDeclaredMethods(); + Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>(); for (Method method : methods) { - if (!method.isBridge()) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - } - } - } + if (!method.isBridge() && AnnotatedElementUtils.isAnnotated(method, annotationType)) { + annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); } } return annotatedMethods; diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java index 4ac99d4d..1c56f4ff 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java @@ -30,14 +30,14 @@ import org.springframework.util.Assert; */ public class StandardClassMetadata implements ClassMetadata { - private final Class introspectedClass; + private final Class<?> introspectedClass; /** * Create a new StandardClassMetadata wrapper for the given Class. * @param introspectedClass the Class to introspect */ - public StandardClassMetadata(Class introspectedClass) { + public StandardClassMetadata(Class<?> introspectedClass) { Assert.notNull(introspectedClass, "Class must not be null"); this.introspectedClass = introspectedClass; } @@ -45,57 +45,68 @@ public class StandardClassMetadata implements ClassMetadata { /** * Return the underlying Class. */ - public final Class getIntrospectedClass() { + public final Class<?> getIntrospectedClass() { return this.introspectedClass; } + @Override public String getClassName() { return this.introspectedClass.getName(); } + @Override public boolean isInterface() { return this.introspectedClass.isInterface(); } + @Override public boolean isAbstract() { return Modifier.isAbstract(this.introspectedClass.getModifiers()); } + @Override public boolean isConcrete() { return !(isInterface() || isAbstract()); } + @Override public boolean isFinal() { return Modifier.isFinal(this.introspectedClass.getModifiers()); } + @Override public boolean isIndependent() { return (!hasEnclosingClass() || (this.introspectedClass.getDeclaringClass() != null && Modifier.isStatic(this.introspectedClass.getModifiers()))); } + @Override public boolean hasEnclosingClass() { return (this.introspectedClass.getEnclosingClass() != null); } + @Override public String getEnclosingClassName() { - Class enclosingClass = this.introspectedClass.getEnclosingClass(); + Class<?> enclosingClass = this.introspectedClass.getEnclosingClass(); return (enclosingClass != null ? enclosingClass.getName() : null); } + @Override public boolean hasSuperClass() { return (this.introspectedClass.getSuperclass() != null); } + @Override public String getSuperClassName() { - Class superClass = this.introspectedClass.getSuperclass(); + Class<?> superClass = this.introspectedClass.getSuperclass(); return (superClass != null ? superClass.getName() : null); } + @Override public String[] getInterfaceNames() { - Class[] ifcs = this.introspectedClass.getInterfaces(); + Class<?>[] ifcs = this.introspectedClass.getInterfaces(); String[] ifcNames = new String[ifcs.length]; for (int i = 0; i < ifcs.length; i++) { ifcNames[i] = ifcs[i].getName(); @@ -103,6 +114,7 @@ public class StandardClassMetadata implements ClassMetadata { return ifcNames; } + @Override public String[] getMemberClassNames() { LinkedHashSet<String> memberClassNames = new LinkedHashSet<String>(); for (Class<?> nestedClass : this.introspectedClass.getDeclaredClasses()) { diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index 8288debe..6899ed7b 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -16,13 +16,13 @@ package org.springframework.core.type; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; /** * {@link MethodMetadata} implementation that uses standard reflection @@ -31,6 +31,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Mark Pollack * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ public class StandardMethodMetadata implements MethodMetadata { @@ -73,56 +74,56 @@ public class StandardMethodMetadata implements MethodMetadata { } + @Override public String getMethodName() { return this.introspectedMethod.getName(); } + @Override public String getDeclaringClassName() { return this.introspectedMethod.getDeclaringClass().getName(); } + @Override public boolean isStatic() { return Modifier.isStatic(this.introspectedMethod.getModifiers()); } + @Override public boolean isFinal() { return Modifier.isFinal(this.introspectedMethod.getModifiers()); } + @Override public boolean isOverridable() { return (!isStatic() && !isFinal() && !Modifier.isPrivate(this.introspectedMethod.getModifiers())); } + @Override public boolean isAnnotated(String annotationType) { - Annotation[] anns = this.introspectedMethod.getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - return false; + return AnnotatedElementUtils.isAnnotated(this.introspectedMethod, annotationType); } + @Override public Map<String, Object> getAnnotationAttributes(String annotationType) { - Annotation[] anns = this.introspectedMethod.getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - ann, true, nestedAnnotationsAsMap); - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - metaAnn, true, this.nestedAnnotationsAsMap); - } - } - } - return null; + return getAnnotationAttributes(annotationType, false); + } + + @Override + public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAnnotationAttributes(this.introspectedMethod, + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); + } + + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod, + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java new file mode 100644 index 00000000..2cc12717 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core.type.classreading; + +import java.lang.reflect.Field; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.util.ReflectionUtils; + +/** + * @author Chris Beams + * @author Juergen Hoeller + * @author Phillip Webb + * @author Sam Brannen + * @since 3.1.1 + */ +abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { + + protected final Log logger = LogFactory.getLog(getClass()); + + protected final AnnotationAttributes attributes; + + protected final ClassLoader classLoader; + + + public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) { + super(SpringAsmInfo.ASM_VERSION); + this.classLoader = classLoader; + this.attributes = attributes; + } + + + @Override + public void visit(String attributeName, Object attributeValue) { + this.attributes.put(attributeName, attributeValue); + } + + @Override + public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { + String annotationType = Type.getType(asmTypeDescriptor).getClassName(); + AnnotationAttributes nestedAttributes = new AnnotationAttributes(); + this.attributes.put(attributeName, nestedAttributes); + return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); + } + + @Override + public AnnotationVisitor visitArray(String attributeName) { + return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader); + } + + @Override + public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) { + Object newValue = getEnumValue(asmTypeDescriptor, attributeValue); + visit(attributeName, newValue); + } + + protected Object getEnumValue(String asmTypeDescriptor, String attributeValue) { + Object valueToUse = attributeValue; + try { + Class<?> enumType = this.classLoader.loadClass(Type.getType(asmTypeDescriptor).getClassName()); + Field enumConstant = ReflectionUtils.findField(enumType, attributeValue); + if (enumConstant != null) { + valueToUse = enumConstant.get(null); + } + } + catch (ClassNotFoundException ex) { + logger.debug("Failed to classload enum type while reading annotation metadata", ex); + } + catch (IllegalAccessException ex) { + logger.warn("Could not access enum value while reading annotation metadata", ex); + } + return valueToUse; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java index 4e9bb8fa..6937e811 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -17,202 +17,16 @@ package org.springframework.core.type.classreading; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.asm.AnnotationVisitor; -import org.springframework.asm.SpringAsmInfo; -import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; - -/** - * @author Chris Beams - * @author Juergen Hoeller - * @since 3.1.1 - */ -abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { - - protected final Log logger = LogFactory.getLog(getClass()); - - protected final AnnotationAttributes attributes; - - protected final ClassLoader classLoader; - - public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) { - super(SpringAsmInfo.ASM_VERSION); - this.classLoader = classLoader; - this.attributes = attributes; - } - - public void visit(String attributeName, Object attributeValue) { - this.attributes.put(attributeName, attributeValue); - } - - public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { - String annotationType = Type.getType(asmTypeDescriptor).getClassName(); - AnnotationAttributes nestedAttributes = new AnnotationAttributes(); - this.attributes.put(attributeName, nestedAttributes); - return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); - } - - public AnnotationVisitor visitArray(String attributeName) { - return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader); - } - - public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) { - Object newValue = getEnumValue(asmTypeDescriptor, attributeValue); - visit(attributeName, newValue); - } - - protected Object getEnumValue(String asmTypeDescriptor, String attributeValue) { - Object valueToUse = attributeValue; - try { - Class<?> enumType = this.classLoader.loadClass(Type.getType(asmTypeDescriptor).getClassName()); - Field enumConstant = ReflectionUtils.findField(enumType, attributeValue); - if (enumConstant != null) { - valueToUse = enumConstant.get(null); - } - } - catch (ClassNotFoundException ex) { - logger.debug("Failed to classload enum type while reading annotation metadata", ex); - } - catch (IllegalAccessException ex) { - logger.warn("Could not access enum value while reading annotation metadata", ex); - } - return valueToUse; - } -} - - -/** - * @author Chris Beams - * @author Juergen Hoeller - * @since 3.1.1 - */ -final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor { - - private final String attributeName; - - private final List<AnnotationAttributes> allNestedAttributes = new ArrayList<AnnotationAttributes>(); - - public RecursiveAnnotationArrayVisitor( - String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) { - super(classLoader, attributes); - this.attributeName = attributeName; - } - - @Override - public void visit(String attributeName, Object attributeValue) { - Object newValue = attributeValue; - Object existingValue = this.attributes.get(this.attributeName); - if (existingValue != null) { - newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); - } - else { - Class<?> arrayClass = newValue.getClass(); - if (Enum.class.isAssignableFrom(arrayClass)) { - while (arrayClass.getSuperclass() != null && !arrayClass.isEnum()) { - arrayClass = arrayClass.getSuperclass(); - } - } - Object[] newArray = (Object[]) Array.newInstance(arrayClass, 1); - newArray[0] = newValue; - newValue = newArray; - } - this.attributes.put(this.attributeName, newValue); - } - - @Override - public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { - String annotationType = Type.getType(asmTypeDescriptor).getClassName(); - AnnotationAttributes nestedAttributes = new AnnotationAttributes(); - this.allNestedAttributes.add(nestedAttributes); - return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); - } - - public void visitEnd() { - if (!this.allNestedAttributes.isEmpty()) { - this.attributes.put(this.attributeName, - this.allNestedAttributes.toArray(new AnnotationAttributes[this.allNestedAttributes.size()])); - } - } -} - - -/** - * @author Chris Beams - * @author Juergen Hoeller - * @since 3.1.1 - */ -class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor { - - private final String annotationType; - - public RecursiveAnnotationAttributesVisitor(String annotationType, AnnotationAttributes attributes, - ClassLoader classLoader) { - super(classLoader, attributes); - this.annotationType = annotationType; - } - - public final void visitEnd() { - try { - Class<?> annotationClass = this.classLoader.loadClass(this.annotationType); - doVisitEnd(annotationClass); - } - catch (ClassNotFoundException ex) { - logger.debug("Failed to class-load type while reading annotation metadata. " + - "This is a non-fatal error, but certain annotation metadata may be unavailable.", ex); - } - } - - protected void doVisitEnd(Class<?> annotationClass) { - registerDefaultValues(annotationClass); - } - - private void registerDefaultValues(Class<?> annotationClass) { - // Only do further scanning for public annotations; we'd run into - // IllegalAccessExceptions otherwise, and we don't want to mess with - // accessibility in a SecurityManager environment. - if (Modifier.isPublic(annotationClass.getModifiers())) { - // Check declared default values of attributes in the annotation type. - Method[] annotationAttributes = annotationClass.getMethods(); - for (Method annotationAttribute : annotationAttributes) { - String attributeName = annotationAttribute.getName(); - Object defaultValue = annotationAttribute.getDefaultValue(); - if (defaultValue != null && !this.attributes.containsKey(attributeName)) { - if (defaultValue instanceof Annotation) { - defaultValue = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes( - (Annotation) defaultValue, false, true)); - } - else if (defaultValue instanceof Annotation[]) { - Annotation[] realAnnotations = (Annotation[]) defaultValue; - AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; - for (int i = 0; i < realAnnotations.length; i++) { - mappedAnnotations[i] = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes( - realAnnotations[i], false, true)); - } - defaultValue = mappedAnnotations; - } - this.attributes.put(attributeName, defaultValue); - } - } - } - } -} - /** * ASM visitor which looks for the annotations defined on a class or method, including @@ -224,19 +38,22 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi * * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb + * @author Sam Brannen * @since 3.0 */ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { private final String annotationType; - private final Map<String, AnnotationAttributes> attributesMap; + private final MultiValueMap<String, AnnotationAttributes> attributesMap; private final Map<String, Set<String>> metaAnnotationMap; - public AnnotationAttributesReadingVisitor( - String annotationType, Map<String, AnnotationAttributes> attributesMap, - Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) { + + public AnnotationAttributesReadingVisitor(String annotationType, + MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap, + ClassLoader classLoader) { super(annotationType, new AnnotationAttributes(), classLoader); this.annotationType = annotationType; @@ -244,27 +61,23 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib this.metaAnnotationMap = metaAnnotationMap; } + @Override public void doVisitEnd(Class<?> annotationClass) { super.doVisitEnd(annotationClass); - this.attributesMap.put(this.annotationType, this.attributes); - registerMetaAnnotations(annotationClass); - } - - private void registerMetaAnnotations(Class<?> annotationClass) { - // Register annotations that the annotation type is annotated with. + List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType); + if (attributes == null) { + this.attributesMap.add(this.annotationType, this.attributes); + } + else { + attributes.add(0, this.attributes); + } Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(); - for (Annotation metaAnnotation : annotationClass.getAnnotations()) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); - // Only do further scanning for public annotations; we'd run into IllegalAccessExceptions - // otherwise, and don't want to mess with accessibility in a SecurityManager environment. - if (Modifier.isPublic(metaAnnotation.annotationType().getModifiers())) { - if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) { - this.attributesMap.put(metaAnnotation.annotationType().getName(), - AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true)); - } - for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) { - metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName()); + Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass); + if (!ObjectUtils.isEmpty(metaAnnotations)) { + for (Annotation metaAnnotation : metaAnnotations) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) { + recursivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation); } } } @@ -273,4 +86,19 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib } } + private void recursivelyCollectMetaAnnotations(Set<String> visited, Annotation annotation) { + String annotationName = annotation.annotationType().getName(); + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) && visited.add(annotationName)) { + // Only do further scanning for public annotations; we'd run into + // IllegalAccessExceptions otherwise, and we don't want to mess with + // accessibility in a SecurityManager environment. + if (Modifier.isPublic(annotation.annotationType().getModifiers())) { + this.attributesMap.add(annotationName, AnnotationUtils.getAnnotationAttributes(annotation, false, true)); + for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations()) { + recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation); + } + } + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index e1739846..62ceb04d 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -19,6 +19,7 @@ package org.springframework.core.type.classreading; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -29,6 +30,8 @@ import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * ASM class visitor which looks for the class name and implemented types as @@ -38,19 +41,26 @@ import org.springframework.core.type.MethodMetadata; * @author Juergen Hoeller * @author Mark Fisher * @author Costin Leau + * @author Phillip Webb + * @author Sam Brannen * @since 2.5 */ -final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { +public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { - private final ClassLoader classLoader; + protected final ClassLoader classLoader; - private final Set<String> annotationSet = new LinkedHashSet<String>(4); + protected final Set<String> annotationSet = new LinkedHashSet<String>(4); - private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4); + protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4); - private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(4); + /** + * Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap} + * to ensure that the hierarchical ordering of the entries is preserved. + * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes + */ + protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4); - private final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4); + protected final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4); public AnnotationMetadataReadingVisitor(ClassLoader classLoader) { @@ -72,22 +82,26 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor public AnnotationVisitor visitAnnotation(final String desc, boolean visible) { String className = Type.getType(desc).getClassName(); this.annotationSet.add(className); - return new AnnotationAttributesReadingVisitor(className, this.attributeMap, this.metaAnnotationMap, this.classLoader); + return new AnnotationAttributesReadingVisitor(className, this.attributesMap, this.metaAnnotationMap, this.classLoader); } + @Override public Set<String> getAnnotationTypes() { return this.annotationSet; } + @Override public Set<String> getMetaAnnotationTypes(String annotationType) { return this.metaAnnotationMap.get(annotationType); } + @Override public boolean hasAnnotation(String annotationType) { return this.annotationSet.contains(annotationType); } + @Override public boolean hasMetaAnnotation(String metaAnnotationType) { Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values(); for (Set<String> metaTypes : allMetaTypes) { @@ -98,71 +112,45 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor return false; } + @Override public boolean isAnnotated(String annotationType) { - return this.attributeMap.containsKey(annotationType); + return this.attributesMap.containsKey(annotationType); } + @Override public AnnotationAttributes getAnnotationAttributes(String annotationType) { return getAnnotationAttributes(annotationType, false); } + @Override public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - AnnotationAttributes raw = this.attributeMap.get(annotationType); - return convertClassValues(raw, classValuesAsString); + AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes( + this.attributesMap, this.metaAnnotationMap, annotationType); + return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString); } - private AnnotationAttributes convertClassValues(AnnotationAttributes original, boolean classValuesAsString) { - if (original == null) { + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) { + MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>(); + List<AnnotationAttributes> attributes = this.attributesMap.get(annotationType); + if (attributes == null) { return null; } - AnnotationAttributes result = new AnnotationAttributes(original.size()); - for (Map.Entry<String, Object> entry : original.entrySet()) { - try { - Object value = entry.getValue(); - if (value instanceof AnnotationAttributes) { - value = convertClassValues((AnnotationAttributes) value, classValuesAsString); - } - else if (value instanceof AnnotationAttributes[]) { - AnnotationAttributes[] values = (AnnotationAttributes[])value; - for (int i = 0; i < values.length; i++) { - values[i] = convertClassValues(values[i], classValuesAsString); - } - } - else if (value instanceof Type) { - value = (classValuesAsString ? ((Type) value).getClassName() : - this.classLoader.loadClass(((Type) value).getClassName())); - } - else if (value instanceof Type[]) { - Type[] array = (Type[]) value; - Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); - for (int i = 0; i < array.length; i++) { - convArray[i] = (classValuesAsString ? array[i].getClassName() : - this.classLoader.loadClass(array[i].getClassName())); - } - value = convArray; - } - else if (classValuesAsString) { - if (value instanceof Class) { - value = ((Class<?>) value).getName(); - } - else if (value instanceof Class[]) { - Class<?>[] clazzArray = (Class[]) value; - String[] newValue = new String[clazzArray.length]; - for (int i = 0; i < clazzArray.length; i++) { - newValue[i] = clazzArray[i].getName(); - } - value = newValue; - } - } - result.put(entry.getKey(), value); - } - catch (Exception ex) { - // Class not found - can't resolve class reference in annotation attribute. + for (AnnotationAttributes raw : attributes) { + for (Map.Entry<String, Object> entry : + AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); } } - return result; + return allAttributes; } + @Override public boolean hasAnnotatedMethods(String annotationType) { for (MethodMetadata methodMetadata : this.methodMetadataSet) { if (methodMetadata.isAnnotated(annotationType)) { @@ -172,6 +160,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor return false; } + @Override public Set<MethodMetadata> getAnnotatedMethods(String annotationType) { Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>(4); for (MethodMetadata methodMetadata : this.methodMetadataSet) { diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java new file mode 100644 index 00000000..2fe25085 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core.type.classreading; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.LinkedMultiValueMap; + +/** + * Internal utility class used when reading annotations. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Costin Leau + * @author Phillip Webb + * @author Sam Brannen + * @since 4.0 + */ +abstract class AnnotationReadingVisitorUtils { + + public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original, + boolean classValuesAsString) { + + if (original == null) { + return null; + } + + AnnotationAttributes result = new AnnotationAttributes(original.size()); + for (Map.Entry<String, Object> entry : original.entrySet()) { + try { + Object value = entry.getValue(); + if (value instanceof AnnotationAttributes) { + value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString); + } + else if (value instanceof AnnotationAttributes[]) { + AnnotationAttributes[] values = (AnnotationAttributes[]) value; + for (int i = 0; i < values.length; i++) { + values[i] = convertClassValues(classLoader, values[i], classValuesAsString); + } + } + else if (value instanceof Type) { + value = (classValuesAsString ? ((Type) value).getClassName() + : classLoader.loadClass(((Type) value).getClassName())); + } + else if (value instanceof Type[]) { + Type[] array = (Type[]) value; + Object[] convArray = (classValuesAsString ? new String[array.length] : new Class<?>[array.length]); + for (int i = 0; i < array.length; i++) { + convArray[i] = (classValuesAsString ? array[i].getClassName() + : classLoader.loadClass(array[i].getClassName())); + } + value = convArray; + } + else if (classValuesAsString) { + if (value instanceof Class) { + value = ((Class<?>) value).getName(); + } + else if (value instanceof Class[]) { + Class<?>[] clazzArray = (Class[]) value; + String[] newValue = new String[clazzArray.length]; + for (int i = 0; i < clazzArray.length; i++) { + newValue[i] = clazzArray[i].getName(); + } + value = newValue; + } + } + result.put(entry.getKey(), value); + } + catch (Exception ex) { + // Class not found - can't resolve class reference in annotation + // attribute. + } + } + return result; + } + + /** + * Retrieve the merged attributes of the annotation of the given type, + * if any, from the supplied {@code attributesMap}. + * <p>Annotation attribute values appearing <em>lower</em> in the annotation + * hierarchy (i.e., closer to the declaring class) will override those + * defined <em>higher</em> in the annotation hierarchy. + * @param attributesMap the map of annotation attribute lists, + * keyed by annotation type name + * @param metaAnnotationMap the map of meta annotation relationships, + * keyed by annotation type name + * @param annotationType the name of the annotation type to look for + * @return the merged annotation attributes, or {@code null} if no + * matching annotation is present in the {@code attributesMap} + * @since 4.0.3 + */ + public static AnnotationAttributes getMergedAnnotationAttributes( + LinkedMultiValueMap<String, AnnotationAttributes> attributesMap, + Map<String, Set<String>> metaAnnotationMap, String annotationType) { + + // Get the unmerged list of attributes for the target annotation. + List<AnnotationAttributes> attributesList = attributesMap.get(annotationType); + if (attributesList == null || attributesList.isEmpty()) { + return null; + } + + // To start with, we populate the results with a copy of all attribute + // values from the target annotation. A copy is necessary so that we do + // not inadvertently mutate the state of the metadata passed to this + // method. + AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0)); + + Set<String> overridableAttributeNames = new HashSet<String>(results.keySet()); + overridableAttributeNames.remove(AnnotationUtils.VALUE); + + // Since the map is a LinkedMultiValueMap, we depend on the ordering of + // elements in the map and reverse the order of the keys in order to traverse + // "down" the annotation hierarchy. + List<String> annotationTypes = new ArrayList<String>(attributesMap.keySet()); + Collections.reverse(annotationTypes); + + // No need to revisit the target annotation type: + annotationTypes.remove(annotationType); + + for (String currentAnnotationType : annotationTypes) { + List<AnnotationAttributes> currentAttributesList = attributesMap.get(currentAnnotationType); + if (currentAttributesList != null && !currentAttributesList.isEmpty()) { + Set<String> metaAnns = metaAnnotationMap.get(currentAnnotationType); + if (metaAnns != null && metaAnns.contains(annotationType)) { + AnnotationAttributes currentAttributes = currentAttributesList.get(0); + for (String overridableAttributeName : overridableAttributeNames) { + Object value = currentAttributes.get(overridableAttributeName); + if (value != null) { + // Store the value, potentially overriding a value from an + // attribute of the same name found higher in the annotation + // hierarchy. + results.put(overridableAttributeName, value); + } + } + } + } + } + + return results; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java index 45785bc8..af3d616b 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,6 +67,7 @@ class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata } + @Override public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) { this.className = ClassUtils.convertResourcePathToClassName(name); this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0); @@ -81,10 +82,12 @@ class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata } } + @Override public void visitOuterClass(String owner, String name, String desc) { this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner); } + @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (outerName != null) { String fqName = ClassUtils.convertResourcePathToClassName(name); @@ -99,115 +102,132 @@ class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata } } + @Override public void visitSource(String source, String debug) { // no-op } + @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { // no-op return new EmptyAnnotationVisitor(); } + @Override public void visitAttribute(Attribute attr) { // no-op } + @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { // no-op return new EmptyFieldVisitor(); } + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { // no-op return new EmptyMethodVisitor(); } + @Override public void visitEnd() { // no-op } + @Override public String getClassName() { return this.className; } + @Override public boolean isInterface() { return this.isInterface; } + @Override public boolean isAbstract() { return this.isAbstract; } + @Override public boolean isConcrete() { return !(this.isInterface || this.isAbstract); } + @Override public boolean isFinal() { return this.isFinal; } + @Override public boolean isIndependent() { return (this.enclosingClassName == null || this.independentInnerClass); } + @Override public boolean hasEnclosingClass() { return (this.enclosingClassName != null); } + @Override public String getEnclosingClassName() { return this.enclosingClassName; } + @Override public boolean hasSuperClass() { return (this.superClassName != null); } + @Override public String getSuperClassName() { return this.superClassName; } + @Override public String[] getInterfaceNames() { return this.interfaces; } + @Override public String[] getMemberClassNames() { return this.memberClassNames.toArray(new String[this.memberClassNames.size()]); } -} + private static class EmptyAnnotationVisitor extends AnnotationVisitor { -class EmptyAnnotationVisitor extends AnnotationVisitor { - - public EmptyAnnotationVisitor() { - super(SpringAsmInfo.ASM_VERSION); - } + public EmptyAnnotationVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } - @Override - public AnnotationVisitor visitAnnotation(String name, String desc) { - return this; - } + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + return this; + } - @Override - public AnnotationVisitor visitArray(String name) { - return this; + @Override + public AnnotationVisitor visitArray(String name) { + return this; + } } -} -class EmptyMethodVisitor extends MethodVisitor { + private static class EmptyMethodVisitor extends MethodVisitor { - public EmptyMethodVisitor() { - super(SpringAsmInfo.ASM_VERSION); + public EmptyMethodVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } } -} -class EmptyFieldVisitor extends FieldVisitor { + private static class EmptyFieldVisitor extends FieldVisitor { - public EmptyFieldVisitor() { - super(SpringAsmInfo.ASM_VERSION); + public EmptyFieldVisitor() { + super(SpringAsmInfo.ASM_VERSION); + } } -}
\ No newline at end of file +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index dde3790c..f88b57b0 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.core.type.classreading; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +27,8 @@ import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.MethodMetadata; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * ASM method visitor which looks for the annotations defined on the method, @@ -37,21 +39,23 @@ import org.springframework.core.type.MethodMetadata; * @author Mark Pollack * @author Costin Leau * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ -final class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata { +public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata { - private final String name; + protected final String name; - private final int access; + protected final int access; - private final String declaringClassName; + protected final String declaringClassName; - private final ClassLoader classLoader; + protected final ClassLoader classLoader; - private final Set<MethodMetadata> methodMetadataSet; + protected final Set<MethodMetadata> methodMetadataSet; - private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(2); + protected final MultiValueMap<String, AnnotationAttributes> attributeMap = + new LinkedMultiValueMap<String, AnnotationAttributes>(4); public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, @@ -73,30 +77,64 @@ final class MethodMetadataReadingVisitor extends MethodVisitor implements Method return new AnnotationAttributesReadingVisitor(className, this.attributeMap, null, this.classLoader); } + @Override public String getMethodName() { return this.name; } + @Override public boolean isStatic() { return ((this.access & Opcodes.ACC_STATIC) != 0); } + @Override public boolean isFinal() { return ((this.access & Opcodes.ACC_FINAL) != 0); } + @Override public boolean isOverridable() { return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0)); } + @Override public boolean isAnnotated(String annotationType) { return this.attributeMap.containsKey(annotationType); } - public AnnotationAttributes getAnnotationAttributes(String annotationType) { - return this.attributeMap.get(annotationType); + @Override + public Map<String, Object> getAnnotationAttributes(String annotationType) { + return getAnnotationAttributes(annotationType, false); + } + + @Override + public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) { + List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType); + return (attributes == null ? null : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, attributes.get(0), classValuesAsString)); + } + + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); } + @Override + public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) { + if (!this.attributeMap.containsKey(annotationType)) { + return null; + } + MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>(); + for (AnnotationAttributes annotationAttributes : this.attributeMap.get(annotationType)) { + for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, annotationAttributes, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); + } + } + return allAttributes; + } + + @Override public String getDeclaringClassName() { return this.declaringClassName; } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java new file mode 100644 index 00000000..3c5bd9a4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core.type.classreading; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.util.ObjectUtils; + +/** + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1.1 + */ +class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor { + + private final String attributeName; + + private final List<AnnotationAttributes> allNestedAttributes = new ArrayList<AnnotationAttributes>(); + + + public RecursiveAnnotationArrayVisitor( + String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) { + + super(classLoader, attributes); + this.attributeName = attributeName; + } + + + @Override + public void visit(String attributeName, Object attributeValue) { + Object newValue = attributeValue; + Object existingValue = this.attributes.get(this.attributeName); + if (existingValue != null) { + newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue); + } + else { + Class<?> arrayClass = newValue.getClass(); + if (Enum.class.isAssignableFrom(arrayClass)) { + while (arrayClass.getSuperclass() != null && !arrayClass.isEnum()) { + arrayClass = arrayClass.getSuperclass(); + } + } + Object[] newArray = (Object[]) Array.newInstance(arrayClass, 1); + newArray[0] = newValue; + newValue = newArray; + } + this.attributes.put(this.attributeName, newValue); + } + + @Override + public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) { + String annotationType = Type.getType(asmTypeDescriptor).getClassName(); + AnnotationAttributes nestedAttributes = new AnnotationAttributes(); + this.allNestedAttributes.add(nestedAttributes); + return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader); + } + + @Override + public void visitEnd() { + if (!this.allNestedAttributes.isEmpty()) { + this.attributes.put(this.attributeName, + this.allNestedAttributes.toArray(new AnnotationAttributes[this.allNestedAttributes.size()])); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java new file mode 100644 index 00000000..2f1be81d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.core.type.classreading; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; + +/** + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1.1 + */ +class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor { + + private final String annotationType; + + + public RecursiveAnnotationAttributesVisitor( + String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) { + + super(classLoader, attributes); + this.annotationType = annotationType; + } + + + @Override + public final void visitEnd() { + try { + Class<?> annotationClass = this.classLoader.loadClass(this.annotationType); + doVisitEnd(annotationClass); + } + catch (ClassNotFoundException ex) { + logger.debug("Failed to class-load type while reading annotation metadata. " + + "This is a non-fatal error, but certain annotation metadata may be unavailable.", ex); + } + } + + protected void doVisitEnd(Class<?> annotationClass) { + registerDefaultValues(annotationClass); + } + + private void registerDefaultValues(Class<?> annotationClass) { + // Only do further scanning for public annotations; we'd run into + // IllegalAccessExceptions otherwise, and we don't want to mess with + // accessibility in a SecurityManager environment. + if (Modifier.isPublic(annotationClass.getModifiers())) { + // Check declared default values of attributes in the annotation type. + Method[] annotationAttributes = annotationClass.getMethods(); + for (Method annotationAttribute : annotationAttributes) { + String attributeName = annotationAttribute.getName(); + Object defaultValue = annotationAttribute.getDefaultValue(); + if (defaultValue != null && !this.attributes.containsKey(attributeName)) { + if (defaultValue instanceof Annotation) { + defaultValue = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes( + (Annotation) defaultValue, false, true)); + } + else if (defaultValue instanceof Annotation[]) { + Annotation[] realAnnotations = (Annotation[]) defaultValue; + AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; + for (int i = 0; i < realAnnotations.length; i++) { + mappedAnnotations[i] = AnnotationAttributes.fromMap( + AnnotationUtils.getAnnotationAttributes(realAnnotations[i], false, true)); + } + defaultValue = mappedAnnotations; + } + this.attributes.put(attributeName, defaultValue); + } + } + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java index 36c495e5..8ffe4c07 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java @@ -70,14 +70,17 @@ final class SimpleMetadataReader implements MetadataReader { } + @Override public Resource getResource() { return this.resource; } + @Override public ClassMetadata getClassMetadata() { return this.classMetadata; } + @Override public AnnotationMetadata getAnnotationMetadata() { return this.annotationMetadata; } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java index 9e1c736f..c4af4541 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java @@ -70,6 +70,7 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory { } + @Override public MetadataReader getMetadataReader(String className) throws IOException { String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; @@ -92,6 +93,7 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory { return getMetadataReader(resource); } + @Override public MetadataReader getMetadataReader(Resource resource) throws IOException { return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader()); } diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java index e63571f6..e2e3d5c1 100644 --- a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java +++ b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java @@ -35,6 +35,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; */ public abstract class AbstractClassTestingTypeFilter implements TypeFilter { + @Override public final boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java index 6ee0c875..b10db84a 100644 --- a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java +++ b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java @@ -18,6 +18,9 @@ package org.springframework.core.type.filter; import java.io.IOException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -36,6 +39,8 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; */ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilter { + protected final Log logger = LogFactory.getLog(getClass()); + private final boolean considerInherited; private final boolean considerInterfaces; @@ -47,6 +52,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte } + @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { @@ -60,40 +66,50 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte return true; } - if (!this.considerInherited) { - return false; - } - if (metadata.hasSuperClass()) { - // Optimization to avoid creating ClassReader for super class. - Boolean superClassMatch = matchSuperClass(metadata.getSuperClassName()); - if (superClassMatch != null) { - if (superClassMatch.booleanValue()) { - return true; - } - } - else { - // Need to read super class to determine a match... - if (match(metadata.getSuperClassName(), metadataReaderFactory)) { - return true; + if (this.considerInherited) { + if (metadata.hasSuperClass()) { + // Optimization to avoid creating ClassReader for super class. + Boolean superClassMatch = matchSuperClass(metadata.getSuperClassName()); + if (superClassMatch != null) { + if (superClassMatch.booleanValue()) { + return true; + } } + else { + // Need to read super class to determine a match... + try { + if (match(metadata.getSuperClassName(), metadataReaderFactory)) { + return true; + } + } + catch (IOException ex) { + logger.debug("Could not read super class [" + metadata.getSuperClassName() + + "] of type-filtered class [" + metadata.getClassName() + "]"); + } + } } } - if (!this.considerInterfaces) { - return false; - } - for (String ifc : metadata.getInterfaceNames()) { - // Optimization to avoid creating ClassReader for super class - Boolean interfaceMatch = matchInterface(ifc); - if (interfaceMatch != null) { - if (interfaceMatch.booleanValue()) { - return true; + if (this.considerInterfaces) { + for (String ifc : metadata.getInterfaceNames()) { + // Optimization to avoid creating ClassReader for super class + Boolean interfaceMatch = matchInterface(ifc); + if (interfaceMatch != null) { + if (interfaceMatch.booleanValue()) { + return true; + } } - } - else { - // Need to read interface to determine a match... - if (match(ifc, metadataReaderFactory)) { - return true; + else { + // Need to read interface to determine a match... + try { + if (match(ifc, metadataReaderFactory)) { + return true; + } + } + catch (IOException ex) { + logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" + + metadata.getClassName() + "]"); + } } } } diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java index 0e2a3a5d..b9b6abdc 100644 --- a/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java +++ b/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java @@ -60,6 +60,7 @@ public class AspectJTypeFilter implements TypeFilter { } + @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { diff --git a/spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java b/spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java new file mode 100644 index 00000000..52ab29a1 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.util; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Random; +import java.util.UUID; + +/** + * A variation of {@link UUID#randomUUID()} that uses {@link SecureRandom} only for the + * initial seed and {@link Random} thereafter. This provides better performance in + * exchange for less securely random id's. + * + * @author Rossen Stoyanchev + * @author Rob Winch + * @since 4.0 + */ +public class AlternativeJdkIdGenerator implements IdGenerator { + + private final Random random; + + + public AlternativeJdkIdGenerator() { + SecureRandom secureRandom = new SecureRandom(); + byte[] seed = new byte[8]; + secureRandom.nextBytes(seed); + this.random = new Random(new BigInteger(seed).longValue()); + } + + + public UUID generateId() { + + byte[] randomBytes = new byte[16]; + this.random.nextBytes(randomBytes); + + long mostSigBits = 0; + for (int i = 0; i < 8; i++) { + mostSigBits = (mostSigBits << 8) | (randomBytes[i] & 0xff); + } + + long leastSigBits = 0; + for (int i = 8; i < 16; i++) { + leastSigBits = (leastSigBits << 8) | (randomBytes[i] & 0xff); + } + + return new UUID(mostSigBits, leastSigBits); + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index a4f74a4b..5108f474 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -53,6 +53,8 @@ public class AntPathMatcher implements PathMatcher { /** Default path separator: "/" */ public static final String DEFAULT_PATH_SEPARATOR = "/"; + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}"); @@ -60,8 +62,11 @@ public class AntPathMatcher implements PathMatcher { private boolean trimTokens = true; - private final Map<String, AntPathStringMatcher> stringMatcherCache = - new ConcurrentHashMap<String, AntPathStringMatcher>(256); + private volatile Boolean cachePatterns; + + private final Map<String, String[]> tokenizedPatternCache = new ConcurrentHashMap<String, String[]>(256); + + final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, AntPathStringMatcher>(256); /** @@ -80,20 +85,43 @@ public class AntPathMatcher implements PathMatcher { this.trimTokens = trimTokens; } + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * <p>Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a reoccurring pattern. + * @see #getStringMatcher(String) + */ + public void setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + } + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + + @Override public boolean isPattern(String path) { return (path.indexOf('*') != -1 || path.indexOf('?') != -1); } + @Override public boolean match(String pattern, String path) { return doMatch(pattern, path, true, null); } + @Override public boolean matchStart(String pattern, String path) { return doMatch(pattern, path, false, null); } - /** * Actually match the given {@code path} against the given {@code pattern}. * @param pattern the pattern to match against @@ -107,7 +135,7 @@ public class AntPathMatcher implements PathMatcher { return false; } - String[] pattDirs = tokenizePath(pattern); + String[] pattDirs = tokenizePattern(pattern); String[] pathDirs = tokenizePath(path); int pattIdxStart = 0; @@ -228,6 +256,35 @@ public class AntPathMatcher implements PathMatcher { } /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * <p>Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns.booleanValue()) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns.booleanValue()) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + /** * Tokenize the given path String into parts, based on this matcher's settings. * @param path the path to tokenize * @return the tokenized path parts @@ -237,20 +294,48 @@ public class AntPathMatcher implements PathMatcher { } /** - * Tests whether or not a string matches against a pattern. The pattern may contain two special characters: - * <br>'*' means zero or more characters - * <br>'?' means one and only one character - * @param pattern pattern to match against. Must not be {@code null}. - * @param str string which must be matched against the pattern. Must not be {@code null}. - * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + * Tests whether or not a string matches against a pattern. + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise */ private boolean matchStrings(String pattern, String str, Map<String, String> uriTemplateVariables) { - AntPathStringMatcher matcher = this.stringMatcherCache.get(pattern); + return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * <p>The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a reoccurring pattern. + * <p>This method may get overridden to implement a custom cache strategy. + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns.booleanValue()) { + matcher = this.stringMatcherCache.get(pattern); + } if (matcher == null) { matcher = new AntPathStringMatcher(pattern); - this.stringMatcherCache.put(pattern, matcher); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns.booleanValue()) { + this.stringMatcherCache.put(pattern, matcher); + } } - return matcher.matchStrings(str, uriTemplateVariables); + return matcher; } /** @@ -266,6 +351,7 @@ public class AntPathMatcher implements PathMatcher { * <p>Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but * does <strong>not</strong> enforce this. */ + @Override public String extractPathWithinPattern(String pattern, String path) { String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, this.trimTokens, true); String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); @@ -296,6 +382,7 @@ public class AntPathMatcher implements PathMatcher { return builder.toString(); } + @Override public Map<String, String> extractUriTemplateVariables(String pattern, String path) { Map<String, String> variables = new LinkedHashMap<String, String>(); boolean result = doMatch(pattern, path, true, variables); @@ -322,14 +409,15 @@ public class AntPathMatcher implements PathMatcher { * @return the combination of the two patterns * @throws IllegalArgumentException when the two patterns cannot be combined */ + @Override public String combine(String pattern1, String pattern2) { if (!StringUtils.hasText(pattern1) && !StringUtils.hasText(pattern2)) { return ""; } - else if (!StringUtils.hasText(pattern1)) { + if (!StringUtils.hasText(pattern1)) { return pattern2; } - else if (!StringUtils.hasText(pattern2)) { + if (!StringUtils.hasText(pattern2)) { return pattern1; } @@ -339,55 +427,37 @@ public class AntPathMatcher implements PathMatcher { // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar return pattern2; } - else if (pattern1.endsWith("/*")) { - if (pattern2.startsWith("/")) { - // /hotels/* + /booking -> /hotels/booking - return pattern1.substring(0, pattern1.length() - 1) + pattern2.substring(1); - } - else { - // /hotels/* + booking -> /hotels/booking - return pattern1.substring(0, pattern1.length() - 1) + pattern2; - } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith("/*")) { + return slashConcat(pattern1.substring(0, pattern1.length() - 2), pattern2); } - else if (pattern1.endsWith("/**")) { - if (pattern2.startsWith("/")) { - // /hotels/** + /booking -> /hotels/**/booking - return pattern1 + pattern2; - } - else { - // /hotels/** + booking -> /hotels/**/booking - return pattern1 + "/" + pattern2; - } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith("/**")) { + return slashConcat(pattern1, pattern2); } - else { - int dotPos1 = pattern1.indexOf('.'); - if (dotPos1 == -1 || pattern1ContainsUriVar) { - // simply concatenate the two patterns - if (pattern1.endsWith("/") || pattern2.startsWith("/")) { - return pattern1 + pattern2; - } - else { - return pattern1 + "/" + pattern2; - } - } - String fileName1 = pattern1.substring(0, dotPos1); - String extension1 = pattern1.substring(dotPos1); - String fileName2; - String extension2; - int dotPos2 = pattern2.indexOf('.'); - if (dotPos2 != -1) { - fileName2 = pattern2.substring(0, dotPos2); - extension2 = pattern2.substring(dotPos2); - } - else { - fileName2 = pattern2; - extension2 = ""; - } - String fileName = fileName1.endsWith("*") ? fileName2 : fileName1; - String extension = extension1.startsWith("*") ? extension2 : extension1; - return fileName + extension; + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1) { + // simply concatenate the two patterns + return slashConcat(pattern1, pattern2); } + String extension1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String fileName2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String extension2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + String extension = extension1.startsWith("*") ? extension2 : extension1; + return fileName2 + extension; + } + + private String slashConcat(String path1, String path2) { + if (path1.endsWith("/") || path2.startsWith("/")) { + return path1 + path2; + } + return path1 + "/" + path2; } /** @@ -402,100 +472,18 @@ public class AntPathMatcher implements PathMatcher { * @param path the full path to use for comparison * @return a comparator capable of sorting patterns in order of explicitness */ + @Override public Comparator<String> getPatternComparator(String path) { return new AntPatternComparator(path); } - private static class AntPatternComparator implements Comparator<String> { - - private final String path; - - private AntPatternComparator(String path) { - this.path = path; - } - - public int compare(String pattern1, String pattern2) { - if (pattern1 == null && pattern2 == null) { - return 0; - } - else if (pattern1 == null) { - return 1; - } - else if (pattern2 == null) { - return -1; - } - boolean pattern1EqualsPath = pattern1.equals(path); - boolean pattern2EqualsPath = pattern2.equals(path); - if (pattern1EqualsPath && pattern2EqualsPath) { - return 0; - } - else if (pattern1EqualsPath) { - return -1; - } - else if (pattern2EqualsPath) { - return 1; - } - int wildCardCount1 = getWildCardCount(pattern1); - int wildCardCount2 = getWildCardCount(pattern2); - - int bracketCount1 = StringUtils.countOccurrencesOf(pattern1, "{"); - int bracketCount2 = StringUtils.countOccurrencesOf(pattern2, "{"); - - int totalCount1 = wildCardCount1 + bracketCount1; - int totalCount2 = wildCardCount2 + bracketCount2; - - if (totalCount1 != totalCount2) { - return totalCount1 - totalCount2; - } - - int pattern1Length = getPatternLength(pattern1); - int pattern2Length = getPatternLength(pattern2); - - if (pattern1Length != pattern2Length) { - return pattern2Length - pattern1Length; - } - - if (wildCardCount1 < wildCardCount2) { - return -1; - } - else if (wildCardCount2 < wildCardCount1) { - return 1; - } - - if (bracketCount1 < bracketCount2) { - return -1; - } - else if (bracketCount2 < bracketCount1) { - return 1; - } - - return 0; - } - - private int getWildCardCount(String pattern) { - if (pattern.endsWith(".*")) { - pattern = pattern.substring(0, pattern.length() - 2); - } - return StringUtils.countOccurrencesOf(pattern, "*"); - } - - /** - * Returns the length of the given pattern, where template variables are considered to be 1 long. - */ - private int getPatternLength(String pattern) { - Matcher m = VARIABLE_PATTERN.matcher(pattern); - return m.replaceAll("#").length(); - } - } - - /** * Tests whether or not a string matches against a pattern via a {@link Pattern}. * <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and * only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>. */ - private static class AntPathStringMatcher { + protected static class AntPathStringMatcher { private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}"); @@ -573,4 +561,97 @@ public class AntPathMatcher implements PathMatcher { } } + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + */ + protected static class AntPatternComparator implements Comparator<String> { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + @Override + public int compare(String pattern1, String pattern2) { + if (isNullOrCaptureAllPattern(pattern1) && isNullOrCaptureAllPattern(pattern2)) { + return 0; + } + else if (isNullOrCaptureAllPattern(pattern1)) { + return 1; + } + else if (isNullOrCaptureAllPattern(pattern2)) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(path); + boolean pattern2EqualsPath = pattern2.equals(path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } + else if (pattern1EqualsPath) { + return -1; + } + else if (pattern2EqualsPath) { + return 1; + } + + int wildCardCount1 = getWildCardCount(pattern1); + int wildCardCount2 = getWildCardCount(pattern2); + + int bracketCount1 = StringUtils.countOccurrencesOf(pattern1, "{"); + int bracketCount2 = StringUtils.countOccurrencesOf(pattern2, "{"); + + int totalCount1 = wildCardCount1 + bracketCount1; + int totalCount2 = wildCardCount2 + bracketCount2; + + if (totalCount1 != totalCount2) { + return totalCount1 - totalCount2; + } + + int pattern1Length = getPatternLength(pattern1); + int pattern2Length = getPatternLength(pattern2); + + if (pattern1Length != pattern2Length) { + return pattern2Length - pattern1Length; + } + + if (wildCardCount1 < wildCardCount2) { + return -1; + } + else if (wildCardCount2 < wildCardCount1) { + return 1; + } + + if (bracketCount1 < bracketCount2) { + return -1; + } + else if (bracketCount2 < bracketCount1) { + return 1; + } + + return 0; + } + + private boolean isNullOrCaptureAllPattern(String pattern) { + return pattern == null || "/**".equals(pattern); + } + + private int getWildCardCount(String pattern) { + if (pattern.endsWith(".*")) { + pattern = pattern.substring(0, pattern.length() - 2); + } + return StringUtils.countOccurrencesOf(pattern, "*"); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + */ + private int getPatternLength(String pattern) { + return VARIABLE_PATTERN.matcher(pattern).replaceAll("#").length(); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/util/Assert.java b/spring-core/src/main/java/org/springframework/util/Assert.java index 55b2325d..0a28cac7 100644 --- a/spring-core/src/main/java/org/springframework/util/Assert.java +++ b/spring-core/src/main/java/org/springframework/util/Assert.java @@ -263,7 +263,7 @@ public abstract class Assert { * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the collection is {@code null} or has no elements */ - public static void notEmpty(Collection collection, String message) { + public static void notEmpty(Collection<?> collection, String message) { if (CollectionUtils.isEmpty(collection)) { throw new IllegalArgumentException(message); } @@ -276,7 +276,7 @@ public abstract class Assert { * @param collection the collection to check * @throws IllegalArgumentException if the collection is {@code null} or has no elements */ - public static void notEmpty(Collection collection) { + public static void notEmpty(Collection<?> collection) { notEmpty(collection, "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); } @@ -289,7 +289,7 @@ public abstract class Assert { * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the map is {@code null} or has no entries */ - public static void notEmpty(Map map, String message) { + public static void notEmpty(Map<?, ?> map, String message) { if (CollectionUtils.isEmpty(map)) { throw new IllegalArgumentException(message); } @@ -302,7 +302,7 @@ public abstract class Assert { * @param map the map to check * @throws IllegalArgumentException if the map is {@code null} or has no entries */ - public static void notEmpty(Map map) { + public static void notEmpty(Map<?, ?> map) { notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); } diff --git a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java index 7a2135fb..9cea1c99 100644 --- a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java +++ b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java @@ -92,31 +92,38 @@ public class AutoPopulatingList<E> implements List<E>, Serializable { } + @Override public void add(int index, E element) { this.backingList.add(index, element); } + @Override public boolean add(E o) { return this.backingList.add(o); } + @Override public boolean addAll(Collection<? extends E> c) { return this.backingList.addAll(c); } + @Override public boolean addAll(int index, Collection<? extends E> c) { return this.backingList.addAll(index, c); } + @Override public void clear() { this.backingList.clear(); } + @Override public boolean contains(Object o) { return this.backingList.contains(o); } - public boolean containsAll(Collection c) { + @Override + public boolean containsAll(Collection<?> c) { return this.backingList.containsAll(c); } @@ -124,6 +131,7 @@ public class AutoPopulatingList<E> implements List<E>, Serializable { * Get the element at the supplied index, creating it if there is * no element at that index. */ + @Override public E get(int index) { int backingListSize = this.backingList.size(); E element = null; @@ -144,62 +152,77 @@ public class AutoPopulatingList<E> implements List<E>, Serializable { return element; } + @Override public int indexOf(Object o) { return this.backingList.indexOf(o); } + @Override public boolean isEmpty() { return this.backingList.isEmpty(); } + @Override public Iterator<E> iterator() { return this.backingList.iterator(); } + @Override public int lastIndexOf(Object o) { return this.backingList.lastIndexOf(o); } + @Override public ListIterator<E> listIterator() { return this.backingList.listIterator(); } + @Override public ListIterator<E> listIterator(int index) { return this.backingList.listIterator(index); } + @Override public E remove(int index) { return this.backingList.remove(index); } + @Override public boolean remove(Object o) { return this.backingList.remove(o); } + @Override public boolean removeAll(Collection<?> c) { return this.backingList.removeAll(c); } + @Override public boolean retainAll(Collection<?> c) { return this.backingList.retainAll(c); } + @Override public E set(int index, E element) { return this.backingList.set(index, element); } + @Override public int size() { return this.backingList.size(); } + @Override public List<E> subList(int fromIndex, int toIndex) { return this.backingList.subList(fromIndex, toIndex); } + @Override public Object[] toArray() { return this.backingList.toArray(); } + @Override public <T> T[] toArray(T[] a) { return this.backingList.toArray(a); } @@ -253,12 +276,13 @@ public class AutoPopulatingList<E> implements List<E>, Serializable { private final Class<? extends E> elementClass; public ReflectiveElementFactory(Class<? extends E> elementClass) { - Assert.notNull(elementClass, "Element clas must not be null"); + Assert.notNull(elementClass, "Element class must not be null"); Assert.isTrue(!elementClass.isInterface(), "Element class must not be an interface type"); Assert.isTrue(!Modifier.isAbstract(elementClass.getModifiers()), "Element class cannot be an abstract class"); this.elementClass = elementClass; } + @Override public E createElement(int index) { try { return this.elementClass.newInstance(); diff --git a/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java b/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java deleted file mode 100644 index d1290588..00000000 --- a/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.util; - -import java.io.Serializable; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; - -/** - * A simple decorator for a Map, encapsulating the workflow for caching - * expensive values in a target Map. Supports caching weak or strong keys. - * - * <p>This class is an abstract template. Caching Map implementations - * should subclass and override the {@code create(key)} method which - * encapsulates expensive creation of a new object. - * - * @author Keith Donald - * @author Juergen Hoeller - * @since 1.2.2 - * @deprecated as of Spring 3.2, to be removed along with LabeledEnum support - */ -@Deprecated -@SuppressWarnings("serial") -public abstract class CachingMapDecorator<K, V> implements Map<K, V>, Serializable { - - private static Object NULL_VALUE = new Object(); - - - private final Map<K, Object> targetMap; - - private final boolean synchronize; - - private final boolean weak; - - - /** - * Create a CachingMapDecorator with strong keys, - * using an underlying synchronized Map. - */ - public CachingMapDecorator() { - this(false); - } - - /** - * Create a CachingMapDecorator, - * using an underlying synchronized Map. - * @param weak whether to use weak references for keys and values - */ - public CachingMapDecorator(boolean weak) { - Map<K, Object> internalMap = (weak ? new WeakHashMap<K, Object>() : new HashMap<K, Object>()); - this.targetMap = Collections.synchronizedMap(internalMap); - this.synchronize = true; - this.weak = weak; - } - - /** - * Create a CachingMapDecorator with initial size, - * using an underlying synchronized Map. - * @param weak whether to use weak references for keys and values - * @param size the initial cache size - */ - public CachingMapDecorator(boolean weak, int size) { - Map<K, Object> internalMap = weak ? new WeakHashMap<K, Object> (size) : new HashMap<K, Object>(size); - this.targetMap = Collections.synchronizedMap(internalMap); - this.synchronize = true; - this.weak = weak; - } - - /** - * Create a CachingMapDecorator for the given Map. - * <p>The passed-in Map won't get synchronized explicitly, - * so make sure to pass in a properly synchronized Map, if desired. - * @param targetMap the Map to decorate - */ - public CachingMapDecorator(Map<K, V> targetMap) { - this(targetMap, false, false); - } - - /** - * Create a CachingMapDecorator for the given Map. - * <p>The passed-in Map won't get synchronized explicitly unless - * you specify "synchronize" as "true". - * @param targetMap the Map to decorate - * @param synchronize whether to synchronize on the given Map - * @param weak whether to use weak references for values - */ - @SuppressWarnings("unchecked") - public CachingMapDecorator(Map<K, V> targetMap, boolean synchronize, boolean weak) { - Assert.notNull(targetMap, "'targetMap' must not be null"); - this.targetMap = (Map<K, Object>) (synchronize ? Collections.synchronizedMap(targetMap) : targetMap); - this.synchronize = synchronize; - this.weak = weak; - } - - - public int size() { - return this.targetMap.size(); - } - - public boolean isEmpty() { - return this.targetMap.isEmpty(); - } - - public boolean containsKey(Object key) { - return this.targetMap.containsKey(key); - } - - public boolean containsValue(Object value) { - Object valueToCheck = (value != null ? value : NULL_VALUE); - if (this.synchronize) { - synchronized (this.targetMap) { - return containsValueOrReference(valueToCheck); - } - } - else { - return containsValueOrReference(valueToCheck); - } - } - - private boolean containsValueOrReference(Object value) { - if (this.targetMap.containsValue(value)) { - return true; - } - for (Object mapVal : this.targetMap.values()) { - if (mapVal instanceof Reference && value.equals(((Reference) mapVal).get())) { - return true; - } - } - return false; - } - - public V remove(Object key) { - return unwrapReturnValue(this.targetMap.remove(key)); - } - - @SuppressWarnings("unchecked") - private V unwrapReturnValue(Object value) { - Object returnValue = value; - if (returnValue instanceof Reference) { - returnValue = ((Reference) returnValue).get(); - } - return (returnValue == NULL_VALUE ? null : (V) returnValue); - } - - public void putAll(Map<? extends K, ? extends V> map) { - this.targetMap.putAll(map); - } - - public void clear() { - this.targetMap.clear(); - } - - public Set<K> keySet() { - if (this.synchronize) { - synchronized (this.targetMap) { - return new LinkedHashSet<K>(this.targetMap.keySet()); - } - } - else { - return new LinkedHashSet<K>(this.targetMap.keySet()); - } - } - - public Collection<V> values() { - if (this.synchronize) { - synchronized (this.targetMap) { - return valuesCopy(); - } - } - else { - return valuesCopy(); - } - } - - @SuppressWarnings("unchecked") - private Collection<V> valuesCopy() { - LinkedList<V> values = new LinkedList<V>(); - for (Iterator<Object> it = this.targetMap.values().iterator(); it.hasNext();) { - Object value = it.next(); - if (value instanceof Reference) { - value = ((Reference) value).get(); - if (value == null) { - it.remove(); - continue; - } - } - values.add(value == NULL_VALUE ? null : (V) value); - } - return values; - } - - public Set<Map.Entry<K, V>> entrySet() { - if (this.synchronize) { - synchronized (this.targetMap) { - return entryCopy(); - } - } - else { - return entryCopy(); - } - } - - @SuppressWarnings("unchecked") - private Set<Map.Entry<K, V>> entryCopy() { - Map<K,V> entries = new LinkedHashMap<K, V>(); - for (Iterator<Entry<K, Object>> it = this.targetMap.entrySet().iterator(); it.hasNext();) { - Entry<K, Object> entry = it.next(); - Object value = entry.getValue(); - if (value instanceof Reference) { - value = ((Reference) value).get(); - if (value == null) { - it.remove(); - continue; - } - } - entries.put(entry.getKey(), value == NULL_VALUE ? null : (V) value); - } - return entries.entrySet(); - } - - - /** - * Put an object into the cache, possibly wrapping it with a weak - * reference. - * @see #useWeakValue(Object, Object) - */ - public V put(K key, V value) { - Object newValue = value; - if (value == null) { - newValue = NULL_VALUE; - } - else if (useWeakValue(key, value)) { - newValue = new WeakReference<Object>(newValue); - } - return unwrapReturnValue(this.targetMap.put(key, newValue)); - } - - /** - * Decide whether to use a weak reference for the value of - * the given key-value pair. - * @param key the candidate key - * @param value the candidate value - * @return {@code true} in order to use a weak reference; - * {@code false} otherwise. - */ - protected boolean useWeakValue(K key, V value) { - return this.weak; - } - - /** - * Get value for key. - * Creates and caches value if it doesn't already exist in the cache. - * <p>This implementation is <i>not</i> synchronized: This is highly - * concurrent but does not guarantee unique instances in the cache, - * as multiple values for the same key could get created in parallel. - * Consider overriding this method to synchronize it, if desired. - * @see #create(Object) - */ - @SuppressWarnings("unchecked") - public V get(Object key) { - Object value = this.targetMap.get(key); - if (value instanceof Reference) { - value = ((Reference) value).get(); - } - if (value == null) { - V newValue = create((K) key); - put((K) key, newValue); - return newValue; - } - return (value == NULL_VALUE ? null : (V) value); - } - - /** - * Create a value to cache for the given key. - * Called by {@code get} if there is no value cached already. - * @param key the cache key - * @see #get(Object) - */ - protected abstract V create(K key); - - - @Override - public String toString() { - return "CachingMapDecorator [" + getClass().getName() + "]:" + this.targetMap; - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 4bac3488..282c020c 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -195,25 +195,6 @@ public abstract class ClassUtils { /** * Replacement for {@code Class.forName()} that also returns Class instances - * for primitives (like "int") and array class names (like "String[]"). - * <p>Always uses the default class loader: that is, preferably the thread context - * class loader, or the ClassLoader that loaded the ClassUtils class as fallback. - * @param name the name of the Class - * @return Class instance for the supplied name - * @throws ClassNotFoundException if the class was not found - * @throws LinkageError if the class file could not be loaded - * @see Class#forName(String, boolean, ClassLoader) - * @see #getDefaultClassLoader() - * @deprecated as of Spring 3.0, in favor of specifying a ClassLoader explicitly: - * see {@link #forName(String, ClassLoader)} - */ - @Deprecated - public static Class<?> forName(String name) throws ClassNotFoundException, LinkageError { - return forName(name, getDefaultClassLoader()); - } - - /** - * Replacement for {@code Class.forName()} that also returns Class instances * for primitives (e.g. "int") and array class names (e.g. "String[]"). * Furthermore, it is also capable of resolving inner class names in Java source * style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State"). @@ -332,19 +313,6 @@ public abstract class ClassUtils { * and can be loaded. Will return {@code false} if either the class or * one of its dependencies is not present or cannot be loaded. * @param className the name of the class to check - * @return whether the specified class is present - * @deprecated as of Spring 2.5, in favor of {@link #isPresent(String, ClassLoader)} - */ - @Deprecated - public static boolean isPresent(String className) { - return isPresent(className, getDefaultClassLoader()); - } - - /** - * Determine whether the {@link Class} identified by the supplied name is present - * and can be loaded. Will return {@code false} if either the class or - * one of its dependencies is not present or cannot be loaded. - * @param className the name of the class to check * @param classLoader the class loader to use * (may be {@code null}, which indicates the default class loader) * @return whether the specified class is present @@ -813,6 +781,26 @@ public abstract class ClassUtils { } /** + * Determine whether the given method is declared by the user or at least pointing to + * a user-declared method. + * <p>Checks {@link Method#isSynthetic()} (for implementation methods) as well as the + * {@code GroovyObject} interface (for interface methods; on an implementation class, + * implementations of the {@code GroovyObject} methods will be marked as synthetic anyway). + * Note that, despite being synthetic, bridge methods ({@link Method#isBridge()}) are considered + * as user-level methods since they are eventually pointing to a user-declared generic method. + * @param method the method to check + * @return {@code true} if the method can be considered as user-declared; [@code false} otherwise + */ + public static boolean isUserLevelMethod(Method method) { + Assert.notNull(method, "Method must not be null"); + return (method.isBridge() || (!method.isSynthetic() && !isGroovyObjectMethod(method))); + } + + private static boolean isGroovyObjectMethod(Method method) { + return method.getDeclaringClass().getName().equals("groovy.lang.GroovyObject"); + } + + /** * Determine whether the given method is overridable in the given target class. * @param method the method to check * @param targetClass the target class to check against @@ -1028,7 +1016,7 @@ public abstract class ClassUtils { * @return a String of form "[com.foo.Bar, com.foo.Baz]" * @see java.util.AbstractCollection#toString() */ - public static String classNamesToString(Class... classes) { + public static String classNamesToString(Class<?>... classes) { return classNamesToString(Arrays.asList(classes)); } @@ -1041,13 +1029,13 @@ public abstract class ClassUtils { * @return a String of form "[com.foo.Bar, com.foo.Baz]" * @see java.util.AbstractCollection#toString() */ - public static String classNamesToString(Collection<Class> classes) { + public static String classNamesToString(Collection<Class<?>> classes) { if (CollectionUtils.isEmpty(classes)) { return "[]"; } StringBuilder sb = new StringBuilder("["); - for (Iterator<Class> it = classes.iterator(); it.hasNext(); ) { - Class clazz = it.next(); + for (Iterator<Class<?>> it = classes.iterator(); it.hasNext(); ) { + Class<?> clazz = it.next(); sb.append(clazz.getName()); if (it.hasNext()) { sb.append(", "); @@ -1103,8 +1091,8 @@ public abstract class ClassUtils { * @return all interfaces that the given object implements as array */ public static Class<?>[] getAllInterfacesForClass(Class<?> clazz, ClassLoader classLoader) { - Set<Class> ifcs = getAllInterfacesForClassAsSet(clazz, classLoader); - return ifcs.toArray(new Class[ifcs.size()]); + Set<Class<?>> ifcs = getAllInterfacesForClassAsSet(clazz, classLoader); + return ifcs.toArray(new Class<?>[ifcs.size()]); } /** @@ -1113,7 +1101,7 @@ public abstract class ClassUtils { * @param instance the instance to analyze for interfaces * @return all interfaces that the given instance implements as Set */ - public static Set<Class> getAllInterfacesAsSet(Object instance) { + public static Set<Class<?>> getAllInterfacesAsSet(Object instance) { Assert.notNull(instance, "Instance must not be null"); return getAllInterfacesForClassAsSet(instance.getClass()); } @@ -1125,7 +1113,7 @@ public abstract class ClassUtils { * @param clazz the class to analyze for interfaces * @return all interfaces that the given object implements as Set */ - public static Set<Class> getAllInterfacesForClassAsSet(Class clazz) { + public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz) { return getAllInterfacesForClassAsSet(clazz, null); } @@ -1138,12 +1126,12 @@ public abstract class ClassUtils { * (may be {@code null} when accepting all declared interfaces) * @return all interfaces that the given object implements as Set */ - public static Set<Class> getAllInterfacesForClassAsSet(Class clazz, ClassLoader classLoader) { + public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz, ClassLoader classLoader) { Assert.notNull(clazz, "Class must not be null"); if (clazz.isInterface() && isVisible(clazz, classLoader)) { - return Collections.singleton(clazz); + return Collections.<Class<?>>singleton(clazz); } - Set<Class> interfaces = new LinkedHashSet<Class>(); + Set<Class<?>> interfaces = new LinkedHashSet<Class<?>>(); while (clazz != null) { Class<?>[] ifcs = clazz.getInterfaces(); for (Class<?> ifc : ifcs) { diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index 5e74473f..edadd3ab 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,34 +42,38 @@ import java.util.Set; public abstract class CollectionUtils { /** - * Return {@code true} if the supplied Collection is {@code null} - * or empty. Otherwise, return {@code false}. + * Return {@code true} if the supplied Collection is {@code null} or empty. + * Otherwise, return {@code false}. * @param collection the Collection to check * @return whether the given Collection is empty */ - public static boolean isEmpty(Collection collection) { + public static boolean isEmpty(Collection<?> collection) { return (collection == null || collection.isEmpty()); } /** - * Return {@code true} if the supplied Map is {@code null} - * or empty. Otherwise, return {@code false}. + * Return {@code true} if the supplied Map is {@code null} or empty. + * Otherwise, return {@code false}. * @param map the Map to check * @return whether the given Map is empty */ - public static boolean isEmpty(Map map) { + public static boolean isEmpty(Map<?, ?> map) { return (map == null || map.isEmpty()); } /** - * Convert the supplied array into a List. A primitive array gets - * converted into a List of the appropriate wrapper type. - * <p>A {@code null} source value will be converted to an - * empty List. + * Convert the supplied array into a List. A primitive array gets converted + * into a List of the appropriate wrapper type. + * <p><b>NOTE:</b> Generally prefer the standard {@link Arrays#asList} method. + * This {@code arrayToList} method is just meant to deal with an incoming Object + * value that might be an {@code Object[]} or a primitive array at runtime. + * <p>A {@code null} source value will be converted to an empty List. * @param source the (potentially primitive) array * @return the converted List result * @see ObjectUtils#toObjectArray(Object) + * @see Arrays#asList(Object[]) */ + @SuppressWarnings("rawtypes") public static List arrayToList(Object source) { return Arrays.asList(ObjectUtils.toObjectArray(source)); } @@ -80,13 +84,13 @@ public abstract class CollectionUtils { * @param collection the target Collection to merge the array into */ @SuppressWarnings("unchecked") - public static void mergeArrayIntoCollection(Object array, Collection collection) { + public static <E> void mergeArrayIntoCollection(Object array, Collection<E> collection) { if (collection == null) { throw new IllegalArgumentException("Collection must not be null"); } Object[] arr = ObjectUtils.toObjectArray(array); for (Object elem : arr) { - collection.add(elem); + collection.add((E) elem); } } @@ -99,19 +103,19 @@ public abstract class CollectionUtils { * @param map the target Map to merge the properties into */ @SuppressWarnings("unchecked") - public static void mergePropertiesIntoMap(Properties props, Map map) { + public static <K, V> void mergePropertiesIntoMap(Properties props, Map<K, V> map) { if (map == null) { throw new IllegalArgumentException("Map must not be null"); } if (props != null) { - for (Enumeration en = props.propertyNames(); en.hasMoreElements();) { + for (Enumeration<?> en = props.propertyNames(); en.hasMoreElements();) { String key = (String) en.nextElement(); Object value = props.getProperty(key); if (value == null) { // Potentially a non-String value... value = props.get(key); } - map.put(key, value); + map.put((K) key, (V) value); } } } @@ -123,7 +127,7 @@ public abstract class CollectionUtils { * @param element the element to look for * @return {@code true} if found, {@code false} else */ - public static boolean contains(Iterator iterator, Object element) { + public static boolean contains(Iterator<?> iterator, Object element) { if (iterator != null) { while (iterator.hasNext()) { Object candidate = iterator.next(); @@ -141,7 +145,7 @@ public abstract class CollectionUtils { * @param element the element to look for * @return {@code true} if found, {@code false} else */ - public static boolean contains(Enumeration enumeration, Object element) { + public static boolean contains(Enumeration<?> enumeration, Object element) { if (enumeration != null) { while (enumeration.hasMoreElements()) { Object candidate = enumeration.nextElement(); @@ -161,7 +165,7 @@ public abstract class CollectionUtils { * @param element the element to look for * @return {@code true} if found, {@code false} else */ - public static boolean containsInstance(Collection collection, Object element) { + public static boolean containsInstance(Collection<?> collection, Object element) { if (collection != null) { for (Object candidate : collection) { if (candidate == element) { @@ -179,7 +183,7 @@ public abstract class CollectionUtils { * @param candidates the candidates to search for * @return whether any of the candidates has been found */ - public static boolean containsAny(Collection source, Collection candidates) { + public static boolean containsAny(Collection<?> source, Collection<?> candidates) { if (isEmpty(source) || isEmpty(candidates)) { return false; } @@ -200,13 +204,14 @@ public abstract class CollectionUtils { * @param candidates the candidates to search for * @return the first present object, or {@code null} if not found */ - public static Object findFirstMatch(Collection source, Collection candidates) { + @SuppressWarnings("unchecked") + public static <E> E findFirstMatch(Collection<?> source, Collection<E> candidates) { if (isEmpty(source) || isEmpty(candidates)) { return null; } for (Object candidate : candidates) { if (source.contains(candidate)) { - return candidate; + return (E) candidate; } } return null; @@ -265,7 +270,7 @@ public abstract class CollectionUtils { * @return {@code true} if the collection contains a single reference or * multiple references to the same instance, {@code false} else */ - public static boolean hasUniqueObject(Collection collection) { + public static boolean hasUniqueObject(Collection<?> collection) { if (isEmpty(collection)) { return false; } @@ -289,7 +294,7 @@ public abstract class CollectionUtils { * @return the common element type, or {@code null} if no clear * common type has been found (or the collection was empty) */ - public static Class<?> findCommonElementType(Collection collection) { + public static Class<?> findCommonElementType(Collection<?> collection) { if (isEmpty(collection)) { return null; } @@ -312,7 +317,7 @@ public abstract class CollectionUtils { * Enumeration elements must be assignable to the type of the given array. The array * returned will be a different instance than the array given. */ - public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) { + public static <A, E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) { ArrayList<A> elements = new ArrayList<A>(); while (enumeration.hasMoreElements()) { elements.add(enumeration.nextElement()); @@ -330,8 +335,7 @@ public abstract class CollectionUtils { } /** - * Adapts a {@code Map<K, List<V>>} to an {@code MultiValueMap<K,V>}. - * + * Adapt a {@code Map<K, List<V>>} to an {@code MultiValueMap<K,V>}. * @param map the map * @return the multi-value map */ @@ -341,8 +345,7 @@ public abstract class CollectionUtils { } /** - * Returns an unmodifiable view of the specified multi-value map. - * + * Return an unmodifiable view of the specified multi-value map. * @param map the map for which an unmodifiable view is to be returned. * @return an unmodifiable view of the specified multi-value map. */ @@ -358,7 +361,6 @@ public abstract class CollectionUtils { } - /** * Iterator wrapping an Enumeration. */ @@ -370,14 +372,17 @@ public abstract class CollectionUtils { this.enumeration = enumeration; } + @Override public boolean hasNext() { return this.enumeration.hasMoreElements(); } + @Override public E next() { return this.enumeration.nextElement(); } + @Override public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException("Not supported"); } @@ -396,6 +401,7 @@ public abstract class CollectionUtils { this.map = map; } + @Override public void add(K key, V value) { List<V> values = this.map.get(key); if (values == null) { @@ -405,23 +411,27 @@ public abstract class CollectionUtils { values.add(value); } + @Override public V getFirst(K key) { List<V> values = this.map.get(key); return (values != null ? values.get(0) : null); } + @Override public void set(K key, V value) { List<V> values = new LinkedList<V>(); values.add(value); this.map.put(key, values); } + @Override public void setAll(Map<K, V> values) { for (Entry<K, V> entry : values.entrySet()) { set(entry.getKey(), entry.getValue()); } } + @Override public Map<K, V> toSingleValueMap() { LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.map.size()); for (Entry<K, List<V>> entry : map.entrySet()) { @@ -430,50 +440,62 @@ public abstract class CollectionUtils { return singleValueMap; } + @Override public int size() { return this.map.size(); } + @Override public boolean isEmpty() { return this.map.isEmpty(); } + @Override public boolean containsKey(Object key) { return this.map.containsKey(key); } + @Override public boolean containsValue(Object value) { return this.map.containsValue(value); } + @Override public List<V> get(Object key) { return this.map.get(key); } + @Override public List<V> put(K key, List<V> value) { return this.map.put(key, value); } + @Override public List<V> remove(Object key) { return this.map.remove(key); } + @Override public void putAll(Map<? extends K, ? extends List<V>> m) { this.map.putAll(m); } + @Override public void clear() { this.map.clear(); } + @Override public Set<K> keySet() { return this.map.keySet(); } + @Override public Collection<List<V>> values() { return this.map.values(); } + @Override public Set<Entry<K, List<V>>> entrySet() { return this.map.entrySet(); } diff --git a/spring-core/src/main/java/org/springframework/util/CompositeIterator.java b/spring-core/src/main/java/org/springframework/util/CompositeIterator.java index 60b1571c..dea6f292 100644 --- a/spring-core/src/main/java/org/springframework/util/CompositeIterator.java +++ b/spring-core/src/main/java/org/springframework/util/CompositeIterator.java @@ -50,6 +50,7 @@ public class CompositeIterator<E> implements Iterator<E> { this.iterators.add(iterator); } + @Override public boolean hasNext() { this.inUse = true; for (Iterator<E> iterator : this.iterators) { @@ -60,6 +61,7 @@ public class CompositeIterator<E> implements Iterator<E> { return false; } + @Override public E next() { this.inUse = true; for (Iterator<E> iterator : this.iterators) { @@ -70,6 +72,7 @@ public class CompositeIterator<E> implements Iterator<E> { throw new NoSuchElementException("All iterators exhausted"); } + @Override public void remove() { throw new UnsupportedOperationException("CompositeIterator does not support remove()"); } diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java index c7324610..3bb6471f 100644 --- a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java +++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java @@ -252,6 +252,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen return put(key, value, true); } + @Override public V putIfAbsent(K key, V value) { return put(key, value, false); } @@ -287,6 +288,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen }); } + @Override public boolean remove(Object key, final Object value) { return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { @Override @@ -300,6 +302,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen }); } + @Override public boolean replace(K key, final V oldValue, final V newValue) { return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { @Override @@ -313,6 +316,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen }); } + @Override public V replace(K key, final V value) { return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { @Override @@ -675,14 +679,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen this.value = value; } + @Override public K getKey() { return this.key; } + @Override public V getValue() { return this.value; } + @Override public V setValue(V value) { V previous = this.value; this.value = value; @@ -842,11 +849,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen moveToNextSegment(); } + @Override public boolean hasNext() { getNextIfNecessary(); return (this.next != null); } + @Override public Entry<K, V> next() { getNextIfNecessary(); if (this.next == null) { @@ -892,6 +901,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen } } + @Override public void remove() { Assert.state(this.last != null); ConcurrentReferenceHashMap.this.remove(this.last.getKey()); @@ -959,14 +969,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen this.nextReference = next; } + @Override public int getHash() { return this.hash; } + @Override public Reference<K, V> getNext() { return this.nextReference; } + @Override public void release() { enqueue(); clear(); @@ -989,14 +1002,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen this.nextReference = next; } + @Override public int getHash() { return this.hash; } + @Override public Reference<K, V> getNext() { return this.nextReference; } + @Override public void release() { enqueue(); clear(); diff --git a/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java b/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java index 969b5e3d..d8338d3b 100644 --- a/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java +++ b/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java @@ -17,6 +17,7 @@ package org.springframework.util; import java.io.Serializable; +import java.util.concurrent.atomic.AtomicInteger; /** * Simple customizable helper class for creating new {@link Thread} instances. @@ -40,9 +41,7 @@ public class CustomizableThreadCreator implements Serializable { private ThreadGroup threadGroup; - private int threadCount = 0; - - private final Object threadCountMonitor = new SerializableMonitor(); + private final AtomicInteger threadCount = new AtomicInteger(0); /** @@ -160,12 +159,7 @@ public class CustomizableThreadCreator implements Serializable { * @see #getThreadNamePrefix() */ protected String nextThreadName() { - int threadNumber = 0; - synchronized (this.threadCountMonitor) { - this.threadCount++; - threadNumber = this.threadCount; - } - return getThreadNamePrefix() + threadNumber; + return getThreadNamePrefix() + this.threadCount.incrementAndGet(); } /** @@ -176,11 +170,4 @@ public class CustomizableThreadCreator implements Serializable { return ClassUtils.getShortName(getClass()) + "-"; } - - /** - * Empty class used for a serializable monitor object. - */ - private static class SerializableMonitor implements Serializable { - } - } diff --git a/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java index 1337a5a5..b4be1bb2 100644 --- a/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java +++ b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java @@ -16,15 +16,11 @@ package org.springframework.util; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; -import java.util.Date; -import java.util.Enumeration; import java.util.Properties; /** @@ -38,7 +34,7 @@ import java.util.Properties; * * <p>Loading from and storing to a stream delegates to {@code Properties.load} * and {@code Properties.store}, respectively, to be fully compatible with - * the Unicode conversion as implemented by the JDK Properties class. On JDK 1.6, + * the Unicode conversion as implemented by the JDK Properties class. As of JDK 1.6, * {@code Properties.load/store} will also be used for readers/writers, * effectively turning this class into a plain backwards compatibility adapter. * @@ -57,175 +53,37 @@ import java.util.Properties; */ public class DefaultPropertiesPersister implements PropertiesPersister { - // Determine whether Properties.load(Reader) is available (on JDK 1.6+) - private static final boolean loadFromReaderAvailable = - ClassUtils.hasMethod(Properties.class, "load", new Class[] {Reader.class}); - - // Determine whether Properties.store(Writer, String) is available (on JDK 1.6+) - private static final boolean storeToWriterAvailable = - ClassUtils.hasMethod(Properties.class, "store", new Class[] {Writer.class, String.class}); - - + @Override public void load(Properties props, InputStream is) throws IOException { props.load(is); } + @Override public void load(Properties props, Reader reader) throws IOException { - if (loadFromReaderAvailable) { - // On JDK 1.6+ - props.load(reader); - } - else { - // Fall back to manual parsing. - doLoad(props, reader); - } - } - - protected void doLoad(Properties props, Reader reader) throws IOException { - BufferedReader in = new BufferedReader(reader); - while (true) { - String line = in.readLine(); - if (line == null) { - return; - } - line = StringUtils.trimLeadingWhitespace(line); - if (line.length() > 0) { - char firstChar = line.charAt(0); - if (firstChar != '#' && firstChar != '!') { - while (endsWithContinuationMarker(line)) { - String nextLine = in.readLine(); - line = line.substring(0, line.length() - 1); - if (nextLine != null) { - line += StringUtils.trimLeadingWhitespace(nextLine); - } - } - int separatorIndex = line.indexOf("="); - if (separatorIndex == -1) { - separatorIndex = line.indexOf(":"); - } - String key = (separatorIndex != -1 ? line.substring(0, separatorIndex) : line); - String value = (separatorIndex != -1) ? line.substring(separatorIndex + 1) : ""; - key = StringUtils.trimTrailingWhitespace(key); - value = StringUtils.trimLeadingWhitespace(value); - props.put(unescape(key), unescape(value)); - } - } - } - } - - protected boolean endsWithContinuationMarker(String line) { - boolean evenSlashCount = true; - int index = line.length() - 1; - while (index >= 0 && line.charAt(index) == '\\') { - evenSlashCount = !evenSlashCount; - index--; - } - return !evenSlashCount; + props.load(reader); } - protected String unescape(String str) { - StringBuilder result = new StringBuilder(str.length()); - for (int index = 0; index < str.length();) { - char c = str.charAt(index++); - if (c == '\\') { - c = str.charAt(index++); - if (c == 't') { - c = '\t'; - } - else if (c == 'r') { - c = '\r'; - } - else if (c == 'n') { - c = '\n'; - } - else if (c == 'f') { - c = '\f'; - } - } - result.append(c); - } - return result.toString(); - } - - + @Override public void store(Properties props, OutputStream os, String header) throws IOException { props.store(os, header); } + @Override public void store(Properties props, Writer writer, String header) throws IOException { - if (storeToWriterAvailable) { - // On JDK 1.6+ - props.store(writer, header); - } - else { - // Fall back to manual parsing. - doStore(props, writer, header); - } - } - - protected void doStore(Properties props, Writer writer, String header) throws IOException { - BufferedWriter out = new BufferedWriter(writer); - if (header != null) { - out.write("#" + header); - out.newLine(); - } - out.write("#" + new Date()); - out.newLine(); - for (Enumeration keys = props.keys(); keys.hasMoreElements();) { - String key = (String) keys.nextElement(); - String val = props.getProperty(key); - out.write(escape(key, true) + "=" + escape(val, false)); - out.newLine(); - } - out.flush(); + props.store(writer, header); } - protected String escape(String str, boolean isKey) { - int len = str.length(); - StringBuilder result = new StringBuilder(len * 2); - for (int index = 0; index < len; index++) { - char c = str.charAt(index); - switch (c) { - case ' ': - if (index == 0 || isKey) { - result.append('\\'); - } - result.append(' '); - break; - case '\\': - result.append("\\\\"); - break; - case '\t': - result.append("\\t"); - break; - case '\n': - result.append("\\n"); - break; - case '\r': - result.append("\\r"); - break; - case '\f': - result.append("\\f"); - break; - default: - if ("=: \t\r\n\f#!".indexOf(c) != -1) { - result.append('\\'); - } - result.append(c); - } - } - return result.toString(); - } - - + @Override public void loadFromXml(Properties props, InputStream is) throws IOException { props.loadFromXML(is); } + @Override public void storeToXml(Properties props, OutputStream os, String header) throws IOException { props.storeToXML(os, header); } + @Override public void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException { props.storeToXML(os, header, encoding); } diff --git a/spring-core/src/main/java/org/springframework/asm/util/package-info.java b/spring-core/src/main/java/org/springframework/util/IdGenerator.java index f03a2e8d..475af0e1 100644 --- a/spring-core/src/main/java/org/springframework/asm/util/package-info.java +++ b/spring-core/src/main/java/org/springframework/util/IdGenerator.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * 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 + * 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, @@ -14,9 +14,22 @@ * limitations under the License. */ +package org.springframework.util; + +import java.util.UUID; + /** - * Dummy implementations of asm-util classes (for internal use only). + * Contract for generating {@link UUID} identifiers. * - * @since 3.2 + * @author Rossen Stoyanchev + * @since 4.0 */ -package org.springframework.asm.util; +public interface IdGenerator { + + /** + * Generate a new identifier. + * @return the generated identifier + */ + UUID generateId(); + +} diff --git a/spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java b/spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java new file mode 100644 index 00000000..7eff6620 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.util; + +/** + * Exception thrown from {@link MimeTypeUtils#parseMimeType(String)} in case of + * encountering an invalid content type specification String. + * + * @author Juergen Hoeller + * @author Rossen Stoyanchev + * @since 4.0 + */ +@SuppressWarnings("serial") +public class InvalidMimeTypeException extends IllegalArgumentException { + + private String mimeType; + + + /** + * Create a new InvalidContentTypeException for the given content type. + * @param mimeType the offending media type + * @param message a detail message indicating the invalid part + */ + public InvalidMimeTypeException(String mimeType, String message) { + super("Invalid mime type \"" + mimeType + "\": " + message); + this.mimeType = mimeType; + + } + + + /** + * Return the offending content type. + */ + public String getMimeType() { + return this.mimeType; + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java index 730047f7..f980b91c 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,6 +70,7 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa // MultiValueMap implementation + @Override public void add(K key, V value) { List<V> values = this.targetMap.get(key); if (values == null) { @@ -79,23 +80,27 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa values.add(value); } + @Override public V getFirst(K key) { List<V> values = this.targetMap.get(key); return (values != null ? values.get(0) : null); } + @Override public void set(K key, V value) { List<V> values = new LinkedList<V>(); values.add(value); this.targetMap.put(key, values); } + @Override public void setAll(Map<K, V> values) { for (Entry<K, V> entry : values.entrySet()) { set(entry.getKey(), entry.getValue()); } } + @Override public Map<K, V> toSingleValueMap() { LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.targetMap.size()); for (Entry<K, List<V>> entry : targetMap.entrySet()) { @@ -107,50 +112,62 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa // Map implementation + @Override public int size() { return this.targetMap.size(); } + @Override public boolean isEmpty() { return this.targetMap.isEmpty(); } + @Override public boolean containsKey(Object key) { return this.targetMap.containsKey(key); } + @Override public boolean containsValue(Object value) { return this.targetMap.containsValue(value); } + @Override public List<V> get(Object key) { return this.targetMap.get(key); } + @Override public List<V> put(K key, List<V> value) { return this.targetMap.put(key, value); } + @Override public List<V> remove(Object key) { return this.targetMap.remove(key); } + @Override public void putAll(Map<? extends K, ? extends List<V>> m) { this.targetMap.putAll(m); } + @Override public void clear() { this.targetMap.clear(); } + @Override public Set<K> keySet() { return this.targetMap.keySet(); } + @Override public Collection<List<V>> values() { return this.targetMap.values(); } + @Override public Set<Entry<K, List<V>>> entrySet() { return this.targetMap.entrySet(); } diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java new file mode 100644 index 00000000..894a0dc5 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -0,0 +1,510 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util; + +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeSet; + +/** + * Represents a MIME Type, as originally defined in RFC 2046 and subsequently used in + * other Internet protocols including HTTP. This class however does not contain support + * the q-parameters used in HTTP content negotiation. Those can be found in the sub-class + * {@code org.springframework.http.MediaType} in the {@code spring-web} module. + * + * <p>Consists of a {@linkplain #getType() type} and a {@linkplain #getSubtype() subtype}. + * Also has functionality to parse media types from a string using + * {@link #valueOf(String)}. For more parsing options see {@link MimeTypeUtils}. + * + * @author Arjen Poutsma + * @author Juergen Hoeller + * @author Rossen Stoyanchev + * @since 4.0 + * @see MimeTypeUtils + */ +public class MimeType implements Comparable<MimeType>, Serializable { + + private static final long serialVersionUID = 4085923477777865903L; + + protected static final String WILDCARD_TYPE = "*"; + + private static final BitSet TOKEN; + + private static final String PARAM_CHARSET = "charset"; + + + private final String type; + + private final String subtype; + + private final Map<String, String> parameters; + + + static { + // variable names refer to RFC 2616, section 2.2 + BitSet ctl = new BitSet(128); + for (int i = 0; i <= 31; i++) { + ctl.set(i); + } + ctl.set(127); + + BitSet separators = new BitSet(128); + separators.set('('); + separators.set(')'); + separators.set('<'); + separators.set('>'); + separators.set('@'); + separators.set(','); + separators.set(';'); + separators.set(':'); + separators.set('\\'); + separators.set('\"'); + separators.set('/'); + separators.set('['); + separators.set(']'); + separators.set('?'); + separators.set('='); + separators.set('{'); + separators.set('}'); + separators.set(' '); + separators.set('\t'); + + TOKEN = new BitSet(128); + TOKEN.set(0, 128); + TOKEN.andNot(ctl); + TOKEN.andNot(separators); + } + + + /** + * Create a new {@code MimeType} for the given primary type. + * <p>The {@linkplain #getSubtype() subtype} is set to "*", parameters empty. + * @param type the primary type + * @throws IllegalArgumentException if any of the parameters contain illegal characters + */ + public MimeType(String type) { + this(type, WILDCARD_TYPE); + } + + /** + * Create a new {@code MimeType} for the given primary type and subtype. + * <p>The parameters are empty. + * @param type the primary type + * @param subtype the subtype + * @throws IllegalArgumentException if any of the parameters contain illegal characters + */ + public MimeType(String type, String subtype) { + this(type, subtype, Collections.<String, String>emptyMap()); + } + + /** + * Create a new {@code MimeType} for the given type, subtype, and character set. + * @param type the primary type + * @param subtype the subtype + * @param charSet the character set + * @throws IllegalArgumentException if any of the parameters contain illegal characters + */ + public MimeType(String type, String subtype, Charset charSet) { + this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name())); + } + + /** + * Copy-constructor that copies the type and subtype of the given {@code MimeType}, + * and allows for different parameter. + * @param other the other media type + * @param parameters the parameters, may be {@code null} + * @throws IllegalArgumentException if any of the parameters contain illegal characters + */ + public MimeType(MimeType other, Map<String, String> parameters) { + this(other.getType(), other.getSubtype(), parameters); + } + + /** + * Create a new {@code MimeType} for the given type, subtype, and parameters. + * @param type the primary type + * @param subtype the subtype + * @param parameters the parameters, may be {@code null} + * @throws IllegalArgumentException if any of the parameters contain illegal characters + */ + public MimeType(String type, String subtype, Map<String, String> parameters) { + Assert.hasLength(type, "type must not be empty"); + Assert.hasLength(subtype, "subtype must not be empty"); + checkToken(type); + checkToken(subtype); + this.type = type.toLowerCase(Locale.ENGLISH); + this.subtype = subtype.toLowerCase(Locale.ENGLISH); + if (!CollectionUtils.isEmpty(parameters)) { + Map<String, String> map = new LinkedCaseInsensitiveMap<String>(parameters.size(), Locale.ENGLISH); + for (Map.Entry<String, String> entry : parameters.entrySet()) { + String attribute = entry.getKey(); + String value = entry.getValue(); + checkParameters(attribute, value); + map.put(attribute, value); + } + this.parameters = Collections.unmodifiableMap(map); + } + else { + this.parameters = Collections.emptyMap(); + } + } + + /** + * Checks the given token string for illegal characters, as defined in RFC 2616, + * section 2.2. + * @throws IllegalArgumentException in case of illegal characters + * @see <a href="http://tools.ietf.org/html/rfc2616#section-2.2">HTTP 1.1, section 2.2</a> + */ + private void checkToken(String token) { + for (int i=0; i < token.length(); i++ ) { + char ch = token.charAt(i); + if (!TOKEN.get(ch)) { + throw new IllegalArgumentException("Invalid token character '" + ch + "' in token \"" + token + "\""); + } + } + } + + protected void checkParameters(String attribute, String value) { + Assert.hasLength(attribute, "parameter attribute must not be empty"); + Assert.hasLength(value, "parameter value must not be empty"); + checkToken(attribute); + if (PARAM_CHARSET.equals(attribute)) { + value = unquote(value); + Charset.forName(value); + } + else if (!isQuotedString(value)) { + checkToken(value); + } + } + + private boolean isQuotedString(String s) { + if (s.length() < 2) { + return false; + } + else { + return ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'"))); + } + } + + protected String unquote(String s) { + if (s == null) { + return null; + } + return isQuotedString(s) ? s.substring(1, s.length() - 1) : s; + } + + /** + * Indicates whether the {@linkplain #getType() type} is the wildcard character + * {@code *} or not. + */ + public boolean isWildcardType() { + return WILDCARD_TYPE.equals(getType()); + } + + /** + * Indicates whether the {@linkplain #getSubtype() subtype} is the wildcard character + * {@code *} or the wildcard character followed by a sufiix (e.g. + * {@code *+xml}), or not. + * @return whether the subtype is {@code *} + */ + public boolean isWildcardSubtype() { + return WILDCARD_TYPE.equals(getSubtype()) || getSubtype().startsWith("*+"); + } + + /** + * Indicates whether this media type is concrete, i.e. whether neither the type or + * subtype is a wildcard character {@code *}. + * @return whether this media type is concrete + */ + public boolean isConcrete() { + return !isWildcardType() && !isWildcardSubtype(); + } + + /** + * Return the primary type. + */ + public String getType() { + return this.type; + } + + /** + * Return the subtype. + */ + public String getSubtype() { + return this.subtype; + } + + /** + * Return the character set, as indicated by a {@code charset} parameter, if any. + * @return the character set, or {@code null} if not available + */ + public Charset getCharSet() { + String charSet = getParameter(PARAM_CHARSET); + return (charSet != null ? Charset.forName(unquote(charSet)) : null); + } + + /** + * Return a generic parameter value, given a parameter name. + * @param name the parameter name + * @return the parameter value, or {@code null} if not present + */ + public String getParameter(String name) { + return this.parameters.get(name); + } + + /** + * Return all generic parameter values. + * @return a read-only map (possibly empty, never {@code null}) + */ + public Map<String, String> getParameters() { + return this.parameters; + } + + /** + * Indicate whether this {@code MediaType} includes the given media type. + * <p>For instance, {@code text/*} includes {@code text/plain} and {@code text/html}, + * and {@code application/*+xml} includes {@code application/soap+xml}, etc. This + * method is <b>not</b> symmetric. + * @param other the reference media type with which to compare + * @return {@code true} if this media type includes the given media type; + * {@code false} otherwise + */ + public boolean includes(MimeType other) { + if (other == null) { + return false; + } + if (this.isWildcardType()) { + // */* includes anything + return true; + } + else if (getType().equals(other.getType())) { + if (getSubtype().equals(other.getSubtype())) { + return true; + } + if (this.isWildcardSubtype()) { + // wildcard with suffix, e.g. application/*+xml + int thisPlusIdx = getSubtype().indexOf('+'); + if (thisPlusIdx == -1) { + return true; + } + else { + // application/*+xml includes application/soap+xml + int otherPlusIdx = other.getSubtype().indexOf('+'); + if (otherPlusIdx != -1) { + String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx); + String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1); + String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1); + if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) { + return true; + } + } + } + } + } + return false; + } + + /** + * Indicate whether this {@code MediaType} is compatible with the given media type. + * <p>For instance, {@code text/*} is compatible with {@code text/plain}, + * {@code text/html}, and vice versa. In effect, this method is similar to + * {@link #includes}, except that it <b>is</b> symmetric. + * @param other the reference media type with which to compare + * @return {@code true} if this media type is compatible with the given media type; + * {@code false} otherwise + */ + public boolean isCompatibleWith(MimeType other) { + if (other == null) { + return false; + } + if (isWildcardType() || other.isWildcardType()) { + return true; + } + else if (getType().equals(other.getType())) { + if (getSubtype().equals(other.getSubtype())) { + return true; + } + // wildcard with suffix? e.g. application/*+xml + if (this.isWildcardSubtype() || other.isWildcardSubtype()) { + + int thisPlusIdx = getSubtype().indexOf('+'); + int otherPlusIdx = other.getSubtype().indexOf('+'); + + if (thisPlusIdx == -1 && otherPlusIdx == -1) { + return true; + } + else if (thisPlusIdx != -1 && otherPlusIdx != -1) { + String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx); + String otherSubtypeNoSuffix = other.getSubtype().substring(0, otherPlusIdx); + + String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1); + String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1); + + if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && + (WILDCARD_TYPE.equals(thisSubtypeNoSuffix) || WILDCARD_TYPE.equals(otherSubtypeNoSuffix))) { + return true; + } + } + } + } + return false; + } + + /** + * Compares this {@code MediaType} to another alphabetically. + * @param other media type to compare to + * @see MimeTypeUtils#sortBySpecificity(List) + */ + @Override + public int compareTo(MimeType other) { + int comp = getType().compareToIgnoreCase(other.getType()); + if (comp != 0) { + return comp; + } + comp = getSubtype().compareToIgnoreCase(other.getSubtype()); + if (comp != 0) { + return comp; + } + comp = getParameters().size() - other.getParameters().size(); + if (comp != 0) { + return comp; + } + TreeSet<String> thisAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); + thisAttributes.addAll(getParameters().keySet()); + TreeSet<String> otherAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); + otherAttributes.addAll(other.getParameters().keySet()); + Iterator<String> thisAttributesIterator = thisAttributes.iterator(); + Iterator<String> otherAttributesIterator = otherAttributes.iterator(); + while (thisAttributesIterator.hasNext()) { + String thisAttribute = thisAttributesIterator.next(); + String otherAttribute = otherAttributesIterator.next(); + comp = thisAttribute.compareToIgnoreCase(otherAttribute); + if (comp != 0) { + return comp; + } + String thisValue = getParameters().get(thisAttribute); + String otherValue = other.getParameters().get(otherAttribute); + if (otherValue == null) { + otherValue = ""; + } + comp = thisValue.compareTo(otherValue); + if (comp != 0) { + return comp; + } + } + return 0; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MimeType)) { + return false; + } + MimeType otherType = (MimeType) other; + return (this.type.equalsIgnoreCase(otherType.type) && + this.subtype.equalsIgnoreCase(otherType.subtype) && + this.parameters.equals(otherType.parameters)); + } + + @Override + public int hashCode() { + int result = this.type.hashCode(); + result = 31 * result + this.subtype.hashCode(); + result = 31 * result + this.parameters.hashCode(); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + appendTo(builder); + return builder.toString(); + } + + protected void appendTo(StringBuilder builder) { + builder.append(this.type); + builder.append('/'); + builder.append(this.subtype); + appendTo(this.parameters, builder); + } + + private void appendTo(Map<String, String> map, StringBuilder builder) { + for (Map.Entry<String, String> entry : map.entrySet()) { + builder.append(';'); + builder.append(entry.getKey()); + builder.append('='); + builder.append(entry.getValue()); + } + } + + /** + * Parse the given String value into a {@code MimeType} object, + * with this method name following the 'valueOf' naming convention + * (as supported by {@link org.springframework.core.convert.ConversionService}. + * @see MimeTypeUtils#parseMimeType(String) + */ + public static MimeType valueOf(String value) { + return MimeTypeUtils.parseMimeType(value); + } + + + public static class SpecificityComparator<T extends MimeType> implements Comparator<T> { + + @Override + public int compare(T mimeType1, T mimeType2) { + if (mimeType1.isWildcardType() && !mimeType2.isWildcardType()) { // */* < audio/* + return 1; + } + else if (mimeType2.isWildcardType() && !mimeType1.isWildcardType()) { // audio/* > */* + return -1; + } + else if (!mimeType1.getType().equals(mimeType2.getType())) { // audio/basic == text/html + return 0; + } + else { // mediaType1.getType().equals(mediaType2.getType()) + if (mimeType1.isWildcardSubtype() && !mimeType2.isWildcardSubtype()) { // audio/* < audio/basic + return 1; + } + else if (mimeType2.isWildcardSubtype() && !mimeType1.isWildcardSubtype()) { // audio/basic > audio/* + return -1; + } + else if (!mimeType1.getSubtype().equals(mimeType2.getSubtype())) { // audio/basic == audio/wave + return 0; + } + else { // mediaType2.getSubtype().equals(mediaType2.getSubtype()) + return compareParameters(mimeType1, mimeType2); + } + } + } + + protected int compareParameters(T mimeType1, T mimeType2) { + int paramsSize1 = mimeType1.getParameters().size(); + int paramsSize2 = mimeType2.getParameters().size(); + return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java new file mode 100644 index 00000000..2b2cd16f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -0,0 +1,328 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.util; + +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.util.MimeType.SpecificityComparator; + +/** + * Miscellaneous {@link MimeType} utility methods. + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class MimeTypeUtils { + + /** + * Public constant mime type that includes all media ranges (i.e. "*/*"). + */ + public static final MimeType ALL; + + /** + * A String equivalent of {@link MimeTypeUtils#ALL}. + */ + public static final String ALL_VALUE = "*/*"; + + /** + * Public constant mime type for {@code application/atom+xml}. + */ + public final static MimeType APPLICATION_ATOM_XML; + + /** + * A String equivalent of {@link MimeTypeUtils#APPLICATION_ATOM_XML}. + */ + public final static String APPLICATION_ATOM_XML_VALUE = "application/atom+xml"; + + /** + * Public constant mime type for {@code application/x-www-form-urlencoded}. + * */ + public final static MimeType APPLICATION_FORM_URLENCODED; + + /** + * A String equivalent of {@link MimeTypeUtils#APPLICATION_FORM_URLENCODED}. + */ + public final static String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded"; + + /** + * Public constant mime type for {@code application/json}. + * */ + public final static MimeType APPLICATION_JSON; + + /** + * A String equivalent of {@link MimeTypeUtils#APPLICATION_JSON}. + */ + public final static String APPLICATION_JSON_VALUE = "application/json"; + + /** + * Public constant mime type for {@code application/octet-stream}. + * */ + public final static MimeType APPLICATION_OCTET_STREAM; + + /** + * A String equivalent of {@link MimeTypeUtils#APPLICATION_OCTET_STREAM}. + */ + public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream"; + + /** + * Public constant mime type for {@code application/xhtml+xml}. + * */ + public final static MimeType APPLICATION_XHTML_XML; + + /** + * A String equivalent of {@link MimeTypeUtils#APPLICATION_XHTML_XML}. + */ + public final static String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml"; + + /** + * Public constant mime type for {@code application/xml}. + */ + public final static MimeType APPLICATION_XML; + + /** + * A String equivalent of {@link MimeTypeUtils#APPLICATION_XML}. + */ + public final static String APPLICATION_XML_VALUE = "application/xml"; + + /** + * Public constant mime type for {@code image/gif}. + */ + public final static MimeType IMAGE_GIF; + + /** + * A String equivalent of {@link MimeTypeUtils#IMAGE_GIF}. + */ + public final static String IMAGE_GIF_VALUE = "image/gif"; + + /** + * Public constant mime type for {@code image/jpeg}. + */ + public final static MimeType IMAGE_JPEG; + + /** + * A String equivalent of {@link MimeTypeUtils#IMAGE_JPEG}. + */ + public final static String IMAGE_JPEG_VALUE = "image/jpeg"; + + /** + * Public constant mime type for {@code image/png}. + */ + public final static MimeType IMAGE_PNG; + + /** + * A String equivalent of {@link MimeTypeUtils#IMAGE_PNG}. + */ + public final static String IMAGE_PNG_VALUE = "image/png"; + + /** + * Public constant mime type for {@code multipart/form-data}. + * */ + public final static MimeType MULTIPART_FORM_DATA; + + /** + * A String equivalent of {@link MimeTypeUtils#MULTIPART_FORM_DATA}. + */ + public final static String MULTIPART_FORM_DATA_VALUE = "multipart/form-data"; + + /** + * Public constant mime type for {@code text/html}. + * */ + public final static MimeType TEXT_HTML; + + /** + * A String equivalent of {@link MimeTypeUtils#TEXT_HTML}. + */ + public final static String TEXT_HTML_VALUE = "text/html"; + + /** + * Public constant mime type for {@code text/plain}. + * */ + public final static MimeType TEXT_PLAIN; + + /** + * A String equivalent of {@link MimeTypeUtils#TEXT_PLAIN}. + */ + public final static String TEXT_PLAIN_VALUE = "text/plain"; + + /** + * Public constant mime type for {@code text/xml}. + * */ + public final static MimeType TEXT_XML; + + /** + * A String equivalent of {@link MimeTypeUtils#TEXT_XML}. + */ + public final static String TEXT_XML_VALUE = "text/xml"; + + + static { + ALL = MimeType.valueOf(ALL_VALUE); + APPLICATION_ATOM_XML = MimeType.valueOf(APPLICATION_ATOM_XML_VALUE); + APPLICATION_FORM_URLENCODED = MimeType.valueOf(APPLICATION_FORM_URLENCODED_VALUE); + APPLICATION_JSON = MimeType.valueOf(APPLICATION_JSON_VALUE); + APPLICATION_OCTET_STREAM = MimeType.valueOf(APPLICATION_OCTET_STREAM_VALUE); + APPLICATION_XHTML_XML = MimeType.valueOf(APPLICATION_XHTML_XML_VALUE); + APPLICATION_XML = MimeType.valueOf(APPLICATION_XML_VALUE); + IMAGE_GIF = MimeType.valueOf(IMAGE_GIF_VALUE); + IMAGE_JPEG = MimeType.valueOf(IMAGE_JPEG_VALUE); + IMAGE_PNG = MimeType.valueOf(IMAGE_PNG_VALUE); + MULTIPART_FORM_DATA = MimeType.valueOf(MULTIPART_FORM_DATA_VALUE); + TEXT_HTML = MimeType.valueOf(TEXT_HTML_VALUE); + TEXT_PLAIN = MimeType.valueOf(TEXT_PLAIN_VALUE); + TEXT_XML = MimeType.valueOf(TEXT_XML_VALUE); + } + + + /** + * Parse the given String into a single {@code MimeType}. + * @param mimeType the string to parse + * @return the mime type + * @throws InvalidMimeTypeException if the string cannot be parsed + */ + public static MimeType parseMimeType(String mimeType) { + if (!StringUtils.hasLength(mimeType)) { + throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty"); + } + String[] parts = StringUtils.tokenizeToStringArray(mimeType, ";"); + + String fullType = parts[0].trim(); + // java.net.HttpURLConnection returns a *; q=.2 Accept header + if (MimeType.WILDCARD_TYPE.equals(fullType)) { + fullType = "*/*"; + } + int subIndex = fullType.indexOf('/'); + if (subIndex == -1) { + throw new InvalidMimeTypeException(mimeType, "does not contain '/'"); + } + if (subIndex == fullType.length() - 1) { + throw new InvalidMimeTypeException(mimeType, "does not contain subtype after '/'"); + } + String type = fullType.substring(0, subIndex); + String subtype = fullType.substring(subIndex + 1, fullType.length()); + if (MimeType.WILDCARD_TYPE.equals(type) && !MimeType.WILDCARD_TYPE.equals(subtype)) { + throw new InvalidMimeTypeException(mimeType, "wildcard type is legal only in '*/*' (all mime types)"); + } + + Map<String, String> parameters = null; + if (parts.length > 1) { + parameters = new LinkedHashMap<String, String>(parts.length - 1); + for (int i = 1; i < parts.length; i++) { + String parameter = parts[i]; + int eqIndex = parameter.indexOf('='); + if (eqIndex != -1) { + String attribute = parameter.substring(0, eqIndex); + String value = parameter.substring(eqIndex + 1, parameter.length()); + parameters.put(attribute, value); + } + } + } + + try { + return new MimeType(type, subtype, parameters); + } + catch (UnsupportedCharsetException ex) { + throw new InvalidMimeTypeException(mimeType, "unsupported charset '" + ex.getCharsetName() + "'"); + } + catch (IllegalArgumentException ex) { + throw new InvalidMimeTypeException(mimeType, ex.getMessage()); + } + } + + /** + * Parse the given, comma-separated string into a list of {@code MimeType} objects. + * @param mimeTypes the string to parse + * @return the list of mime types + * @throws IllegalArgumentException if the string cannot be parsed + */ + public static List<MimeType> parseMimeTypes(String mimeTypes) { + if (!StringUtils.hasLength(mimeTypes)) { + return Collections.emptyList(); + } + String[] tokens = mimeTypes.split(",\\s*"); + List<MimeType> result = new ArrayList<MimeType>(tokens.length); + for (String token : tokens) { + result.add(parseMimeType(token)); + } + return result; + } + + /** + * Return a string representation of the given list of {@code MimeType} objects. + * @param mimeTypes the string to parse + * @return the list of mime types + * @throws IllegalArgumentException if the String cannot be parsed + */ + public static String toString(Collection<? extends MimeType> mimeTypes) { + StringBuilder builder = new StringBuilder(); + for (Iterator<? extends MimeType> iterator = mimeTypes.iterator(); iterator.hasNext();) { + MimeType mimeType = iterator.next(); + mimeType.appendTo(builder); + if (iterator.hasNext()) { + builder.append(", "); + } + } + return builder.toString(); + } + + + /** + * Sorts the given list of {@code MimeType} objects by specificity. + * <p>Given two mime types: + * <ol> + * <li>if either mime type has a {@linkplain MimeType#isWildcardType() wildcard type}, + * then the mime type without the wildcard is ordered before the other.</li> + * <li>if the two mime types have different {@linkplain MimeType#getType() types}, + * then they are considered equal and remain their current order.</li> + * <li>if either mime type has a {@linkplain MimeType#isWildcardSubtype() wildcard subtype} + * , then the mime type without the wildcard is sorted before the other.</li> + * <li>if the two mime types have different {@linkplain MimeType#getSubtype() subtypes}, + * then they are considered equal and remain their current order.</li> + * <li>if the two mime types have a different amount of + * {@linkplain MimeType#getParameter(String) parameters}, then the mime type with the most + * parameters is ordered before the other.</li> + * </ol> + * <p>For example: <blockquote>audio/basic < audio/* < */*</blockquote> + * <blockquote>audio/basic;level=1 < audio/basic</blockquote> + * <blockquote>audio/basic == text/html</blockquote> <blockquote>audio/basic == + * audio/wave</blockquote> + * @param mimeTypes the list of mime types to be sorted + * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.1">HTTP 1.1, section + * 14.1</a> + */ + public static void sortBySpecificity(List<MimeType> mimeTypes) { + Assert.notNull(mimeTypes, "'mimeTypes' must not be null"); + if (mimeTypes.size() > 1) { + Collections.sort(mimeTypes, SPECIFICITY_COMPARATOR); + } + } + + + /** + * Comparator used by {@link #sortBySpecificity(List)}. + */ + public static final Comparator<MimeType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MimeType>(); + +} diff --git a/spring-core/src/main/java/org/springframework/util/NumberUtils.java b/spring-core/src/main/java/org/springframework/util/NumberUtils.java index 47f38a0b..8236c9ed 100644 --- a/spring-core/src/main/java/org/springframework/util/NumberUtils.java +++ b/spring-core/src/main/java/org/springframework/util/NumberUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,11 @@ import java.text.ParseException; */ public abstract class NumberUtils { + private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE); + + private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); + + /** * Convert the given number into an instance of the given target class. * @param number the number to convert @@ -81,6 +86,17 @@ public abstract class NumberUtils { return (T) new Integer(number.intValue()); } else if (targetClass.equals(Long.class)) { + BigInteger bigInt = null; + if (number instanceof BigInteger) { + bigInt = (BigInteger) number; + } + else if (number instanceof BigDecimal) { + bigInt = ((BigDecimal) number).toBigInteger(); + } + // Effectively analogous to JDK 8's BigInteger.longValueExact() + if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) { + raiseOverflowException(number, targetClass); + } return (T) new Long(number.longValue()); } else if (targetClass.equals(BigInteger.class)) { @@ -115,7 +131,7 @@ public abstract class NumberUtils { * @param number the number we tried to convert * @param targetClass the target class we tried to convert to */ - private static void raiseOverflowException(Number number, Class targetClass) { + private static void raiseOverflowException(Number number, Class<?> targetClass) { throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" + number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow"); } diff --git a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java index 1ce878f8..b5c749fb 100644 --- a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java +++ b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java @@ -107,6 +107,7 @@ public class PropertyPlaceholderHelper { public String replacePlaceholders(String value, final Properties properties) { Assert.notNull(properties, "'properties' must not be null"); return replacePlaceholders(value, new PlaceholderResolver() { + @Override public String resolvePlaceholder(String placeholderName) { return properties.getProperty(placeholderName); } diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index 502d6827..87b7c664 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -515,6 +515,7 @@ public abstract class ReflectionUtils { public static Method[] getAllDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException { final List<Method> methods = new ArrayList<Method>(32); doWithMethods(leafClass, new MethodCallback() { + @Override public void doWith(Method method) { methods.add(method); } @@ -530,6 +531,7 @@ public abstract class ReflectionUtils { public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException { final List<Method> methods = new ArrayList<Method>(32); doWithMethods(leafClass, new MethodCallback() { + @Override public void doWith(Method method) { boolean knownSignature = false; Method methodBeingOverriddenWithCovariantReturnType = null; @@ -630,6 +632,7 @@ public abstract class ReflectionUtils { "] must be same or subclass as source class [" + src.getClass().getName() + "]"); } doWithFields(src.getClass(), new FieldCallback() { + @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { makeAccessible(field); Object srcValue = field.get(src); @@ -696,6 +699,7 @@ public abstract class ReflectionUtils { */ public static FieldFilter COPYABLE_FIELDS = new FieldFilter() { + @Override public boolean matches(Field field) { return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())); } @@ -707,6 +711,7 @@ public abstract class ReflectionUtils { */ public static MethodFilter NON_BRIDGED_METHODS = new MethodFilter() { + @Override public boolean matches(Method method) { return !method.isBridge(); } @@ -719,6 +724,7 @@ public abstract class ReflectionUtils { */ public static MethodFilter USER_DECLARED_METHODS = new MethodFilter() { + @Override public boolean matches(Method method) { return (!method.isBridge() && method.getDeclaringClass() != Object.class); } diff --git a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java new file mode 100644 index 00000000..78f29574 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util; + +import java.io.ByteArrayOutputStream; + +/** + * An extension of {@link java.io.ByteArrayOutputStream} that: + * <ul> + * <li>has public {@link org.springframework.util.ResizableByteArrayOutputStream#grow(int)} + * and {@link org.springframework.util.ResizableByteArrayOutputStream#resize(int)} methods + * to get more control over the the size of the internal buffer</li> + * <li>has a higher initial capacity (256) by default</li> + * </ul> + * + * @author Brian Clozel + * @author Juergen Hoeller + * @since 4.0.3 + */ +public class ResizableByteArrayOutputStream extends ByteArrayOutputStream { + + private static final int DEFAULT_INITIAL_CAPACITY = 256; + + + /** + * Create a new <code>ResizableByteArrayOutputStream</code> + * with the default initial capacity of 128 bytes. + */ + public ResizableByteArrayOutputStream() { + super(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Create a new <code>ResizableByteArrayOutputStream</code> + * with the specified initial capacity. + * @param initialCapacity the initial buffer size in bytes + */ + public ResizableByteArrayOutputStream(int initialCapacity) { + super(initialCapacity); + } + + + /** + * Resize the internal buffer size to a specified capacity. + * @param targetCapacity the desired size of the buffer + * @throws IllegalArgumentException if the given capacity is smaller than + * the actual size of the content stored in the buffer already + * @see ResizableByteArrayOutputStream#size() + */ + public synchronized void resize(int targetCapacity) { + Assert.isTrue(targetCapacity >= this.count, "New capacity must not be smaller than current size"); + byte[] resizedBuffer = new byte[targetCapacity]; + System.arraycopy(this.buf, 0, resizedBuffer, 0, this.count); + this.buf = resizedBuffer; + } + + /** + * Grow the internal buffer size. + * @param additionalCapacity the number of bytes to add to the current buffer size + * @see ResizableByteArrayOutputStream#size() + */ + public synchronized void grow(int additionalCapacity) { + Assert.isTrue(additionalCapacity >= 0, "Additional capacity must be 0 or higher"); + if (this.count + additionalCapacity > this.buf.length) { + int newCapacity = Math.max(this.buf.length * 2, this.count + additionalCapacity); + resize(newCapacity); + } + } + + /** + * Return the current size of this stream's internal buffer. + */ + public synchronized int capacity() { + return this.buf.length; + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java index ce552eb0..855a8ca5 100644 --- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java @@ -78,9 +78,6 @@ public abstract class ResourceUtils { /** URL protocol for a general JBoss VFS resource: "vfs" */ public static final String URL_PROTOCOL_VFS = "vfs"; - /** URL protocol for an entry from an OC4J jar file: "code-source" */ - public static final String URL_PROTOCOL_CODE_SOURCE = "code-source"; - /** Separator between JAR URL and file path within the JAR */ public static final String JAR_URL_SEPARATOR = "!/"; @@ -266,18 +263,14 @@ public abstract class ResourceUtils { /** * Determine whether the given URL points to a resource in a jar file, - * that is, has protocol "jar", "zip", "vfszip", "wsjar" or "code-source". - * <p>"zip" and "wsjar" are used by WebLogic Server and WebSphere, respectively, - * but can be treated like jar files. The same applies to "code-source" URLs on - * OC4J, provided that the path contains a jar separator. + * that is, has protocol "jar", "zip", "vfszip" or "wsjar". * @param url the URL to check * @return whether the URL has been identified as a JAR URL */ public static boolean isJarURL(URL url) { String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || - URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) || - (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR))); + URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol)); } /** diff --git a/spring-core/src/main/java/org/springframework/util/SocketUtils.java b/spring-core/src/main/java/org/springframework/util/SocketUtils.java new file mode 100644 index 00000000..bd451250 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/SocketUtils.java @@ -0,0 +1,304 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util; + +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.util.Random; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.net.ServerSocketFactory; + +/** + * Simple utility methods for working with network sockets — for example, + * for finding available ports on {@code localhost}. + * + * <p>Within this class, a TCP port refers to a port for a {@link ServerSocket}; + * whereas, a UDP port refers to a port for a {@link DatagramSocket}. + * + * @author Sam Brannen + * @author Ben Hale + * @author Arjen Poutsma + * @author Gunnar Hillert + * @since 4.0 + */ +public class SocketUtils { + + /** + * The default minimum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MIN = 1024; + + /** + * The default maximum value for port ranges used when finding an available + * socket port. + */ + public static final int PORT_RANGE_MAX = 65535; + + + private static final Random random = new Random(System.currentTimeMillis()); + + + /** + * Although {@code SocketUtils} consists solely of static utility methods, + * this constructor is intentionally {@code public}. + * <h4>Rationale</h4> + * <p>Static methods from this class may be invoked from within XML + * configuration files using the Spring Expression Language (SpEL) and the + * following syntax. + * <pre><code><bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" /></code></pre> + * If this constructor were {@code private}, you would be required to supply + * the fully qualified class name to SpEL's {@code T()} function for each usage. + * Thus, the fact that this constructor is {@code public} allows you to reduce + * boilerplate configuration with SpEL as can be seen in the following example. + * <pre><code><bean id="socketUtils" class="org.springframework.util.SocketUtils" /> + * <bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" /> + * <bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" /></code></pre> + */ + public SocketUtils() { + /* no-op */ + } + + + /** + * Find an available TCP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort() { + return findAvailableTcpPort(PORT_RANGE_MIN); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort) { + return findAvailableTcpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available TCP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableTcpPort(int minPort, int maxPort) { + return SocketType.TCP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @param numRequested the number of available ports to find + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet<Integer> findAvailableTcpPorts(int numRequested) { + return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available TCP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available TCP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet<Integer> findAvailableTcpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort() { + return findAvailableUdpPort(PORT_RANGE_MIN); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort) { + return findAvailableUdpPort(minPort, PORT_RANGE_MAX); + } + + /** + * Find an available UDP port randomly selected from the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available UDP port number + * @throws IllegalStateException if no available port could be found + */ + public static int findAvailableUdpPort(int minPort, int maxPort) { + return SocketType.UDP.findAvailablePort(minPort, maxPort); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @param numRequested the number of available ports to find + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet<Integer> findAvailableUdpPorts(int numRequested) { + return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX); + } + + /** + * Find the requested number of available UDP ports, each randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available UDP port numbers + * @throws IllegalStateException if the requested number of available ports could not be found + */ + public static SortedSet<Integer> findAvailableUdpPorts(int numRequested, int minPort, int maxPort) { + return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort); + } + + + private static enum SocketType { + + TCP { + @Override + protected boolean isPortAvailable(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port); + serverSocket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + }, + + UDP { + @Override + protected boolean isPortAvailable(int port) { + try { + DatagramSocket socket = new DatagramSocket(port); + socket.close(); + return true; + } + catch (Exception ex) { + return false; + } + } + }; + + /** + * Determine if the specified port for this {@code SocketType} is + * currently available on {@code localhost}. + */ + protected abstract boolean isPortAvailable(int port); + + /** + * Find a pseudo-random port number within the range + * [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a random port number within the specified range + */ + private int findRandomPort(int minPort, int maxPort) { + int portRange = maxPort - minPort; + return minPort + random.nextInt(portRange); + } + + /** + * Find an available port for this {@code SocketType}, randomly selected + * from the range [{@code minPort}, {@code maxPort}]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available port number for this socket type + * @throws IllegalStateException if no available port could be found + */ + int findAvailablePort(int minPort, int maxPort) { + Assert.isTrue(minPort > 0, "'minPort' must be greater than 0"); + Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'"); + Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + + int portRange = maxPort - minPort; + int candidatePort; + int searchCounter = 0; + do { + if (++searchCounter > portRange) { + throw new IllegalStateException(String.format( + "Could not find an available %s port in the range [%d, %d] after %d attempts", name(), minPort, + maxPort, searchCounter)); + } + candidatePort = findRandomPort(minPort, maxPort); + } + while (!isPortAvailable(candidatePort)); + + return candidatePort; + } + + /** + * Find the requested number of available ports for this {@code SocketType}, + * each randomly selected from the range [{@code minPort}, {@code maxPort}]. + * @param numRequested the number of available ports to find + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a sorted set of available port numbers for this socket type + * @throws IllegalStateException if the requested number of available ports could not be found + */ + SortedSet<Integer> findAvailablePorts(int numRequested, int minPort, int maxPort) { + Assert.isTrue(minPort > 0, "'minPort' must be greater than 0"); + Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'"); + Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX); + Assert.isTrue(numRequested > 0, "'numRequested' must be greater than 0"); + Assert.isTrue((maxPort - minPort) >= numRequested, + "'numRequested' must not be greater than 'maxPort' - 'minPort'"); + + final SortedSet<Integer> availablePorts = new TreeSet<Integer>(); + int attemptCount = 0; + while ((++attemptCount <= numRequested + 100) && (availablePorts.size() < numRequested)) { + availablePorts.add(findAvailablePort(minPort, maxPort)); + } + + if (availablePorts.size() != numRequested) { + throw new IllegalStateException(String.format( + "Could not find %d available %s ports in the range [%d, %d]", numRequested, name(), minPort, + maxPort)); + } + + return availablePorts; + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/StreamUtils.java b/spring-core/src/main/java/org/springframework/util/StreamUtils.java index cc3107d8..0556c71e 100644 --- a/spring-core/src/main/java/org/springframework/util/StreamUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java @@ -27,7 +27,6 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; - /** * Simple utility methods for dealing with streams. The copy methods of this class are * similar to those defined in {@link FileCopyUtils} except that all affected streams are diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 0ad561da..9ed12a1e 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -28,6 +28,7 @@ import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +import java.util.TimeZone; import java.util.TreeSet; /** @@ -724,6 +725,22 @@ public abstract class StringUtils { return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : ""); } + /** + * Parse the given {@code timeZoneString} value into a {@link TimeZone}. + * @param timeZoneString the time zone String, following {@link TimeZone#getTimeZone(String)} + * but throwing {@link IllegalArgumentException} in case of an invalid time zone specification + * @return a corresponding {@link TimeZone} instance + * @throws IllegalArgumentException in case of an invalid time zone specification + */ + public static TimeZone parseTimeZoneString(String timeZoneString) { + TimeZone timeZone = TimeZone.getTimeZone(timeZoneString); + if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) { + // We don't want that GMT fallback... + throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'"); + } + return timeZone; + } + //--------------------------------------------------------------------- // Convenience methods for working with String arrays diff --git a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java index 6c3acd7e..23ddd15c 100644 --- a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java +++ b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java @@ -93,6 +93,7 @@ public abstract class SystemPropertyUtils { this.text = text; } + @Override public String resolvePlaceholder(String placeholderName) { try { String propVal = System.getProperty(placeholderName); diff --git a/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java b/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java index ad2e01c5..1a486757 100644 --- a/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java +++ b/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ public class WeakReferenceMonitor { private static final ReferenceQueue<Object> handleQueue = new ReferenceQueue<Object>(); // All tracked entries (WeakReference => ReleaseListener) - private static final Map<Reference, ReleaseListener> trackedEntries = new HashMap<Reference, ReleaseListener>(); + private static final Map<Reference<?>, ReleaseListener> trackedEntries = new HashMap<Reference<?>, ReleaseListener>(); // Thread polling handleQueue, lazy initialized private static Thread monitoringThread = null; @@ -84,7 +84,7 @@ public class WeakReferenceMonitor { * @param ref reference to tracked handle * @param entry the associated entry */ - private static void addEntry(Reference ref, ReleaseListener entry) { + private static void addEntry(Reference<?> ref, ReleaseListener entry) { synchronized (WeakReferenceMonitor.class) { // Add entry, the key is given reference. trackedEntries.put(ref, entry); @@ -103,7 +103,7 @@ public class WeakReferenceMonitor { * @param reference the reference that should be removed * @return entry object associated with given reference */ - private static ReleaseListener removeEntry(Reference reference) { + private static ReleaseListener removeEntry(Reference<?> reference) { synchronized (WeakReferenceMonitor.class) { return trackedEntries.remove(reference); } @@ -132,12 +132,13 @@ public class WeakReferenceMonitor { */ private static class MonitoringProcess implements Runnable { + @Override public void run() { logger.debug("Starting reference monitor thread"); // Check if there are any tracked entries left. while (keepMonitoringThreadAlive()) { try { - Reference reference = handleQueue.remove(); + Reference<?> reference = handleQueue.remove(); // Stop tracking this reference. ReleaseListener entry = removeEntry(reference); if (entry != null) { diff --git a/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java index 6fd0c229..ec68351e 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java @@ -59,6 +59,7 @@ public final class BooleanComparator implements Comparator<Boolean>, Serializabl } + @Override public int compare(Boolean v1, Boolean v2) { return (v1 ^ v2) ? ((v1 ^ this.trueLow) ? 1 : -1) : 0; } diff --git a/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java index 40e4e7af..b8b27418 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java @@ -32,6 +32,7 @@ public class ComparableComparator<T extends Comparable<T>> implements Comparator @SuppressWarnings("rawtypes") public static final ComparableComparator INSTANCE = new ComparableComparator(); + @Override public int compare(T o1, T o2) { return o1.compareTo(o2); } diff --git a/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java index ba56bd54..a20eab18 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java @@ -37,10 +37,10 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @since 1.2.2 */ -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "rawtypes" }) public class CompoundComparator<T> implements Comparator<T>, Serializable { - private final List<InvertibleComparator<T>> comparators; + private final List<InvertibleComparator> comparators; /** @@ -49,7 +49,7 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { * IllegalStateException is thrown. */ public CompoundComparator() { - this.comparators = new ArrayList<InvertibleComparator<T>>(); + this.comparators = new ArrayList<InvertibleComparator>(); } /** @@ -62,7 +62,7 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { @SuppressWarnings({ "unchecked", "rawtypes" }) public CompoundComparator(Comparator... comparators) { Assert.notNull(comparators, "Comparators must not be null"); - this.comparators = new ArrayList<InvertibleComparator<T>>(comparators.length); + this.comparators = new ArrayList<InvertibleComparator>(comparators.length); for (Comparator comparator : comparators) { this.addComparator(comparator); } @@ -76,12 +76,13 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { * @param comparator the Comparator to add to the end of the chain * @see InvertibleComparator */ - public void addComparator(Comparator<T> comparator) { + @SuppressWarnings("unchecked") + public void addComparator(Comparator<? extends T> comparator) { if (comparator instanceof InvertibleComparator) { - this.comparators.add((InvertibleComparator<T>) comparator); + this.comparators.add((InvertibleComparator) comparator); } else { - this.comparators.add(new InvertibleComparator<T>(comparator)); + this.comparators.add(new InvertibleComparator(comparator)); } } @@ -90,8 +91,9 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { * @param comparator the Comparator to add to the end of the chain * @param ascending the sort order: ascending (true) or descending (false) */ - public void addComparator(Comparator<T> comparator, boolean ascending) { - this.comparators.add(new InvertibleComparator<T>(comparator, ascending)); + @SuppressWarnings("unchecked") + public void addComparator(Comparator<? extends T> comparator, boolean ascending) { + this.comparators.add(new InvertibleComparator(comparator, ascending)); } /** @@ -102,12 +104,13 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { * @param comparator the Comparator to place at the given index * @see InvertibleComparator */ - public void setComparator(int index, Comparator<T> comparator) { + @SuppressWarnings("unchecked") + public void setComparator(int index, Comparator<? extends T> comparator) { if (comparator instanceof InvertibleComparator) { - this.comparators.set(index, (InvertibleComparator<T>) comparator); + this.comparators.set(index, (InvertibleComparator) comparator); } else { - this.comparators.set(index, new InvertibleComparator<T>(comparator)); + this.comparators.set(index, new InvertibleComparator(comparator)); } } @@ -126,7 +129,7 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { * comparator. */ public void invertOrder() { - for (InvertibleComparator<T> comparator : this.comparators) { + for (InvertibleComparator comparator : this.comparators) { comparator.invertOrder(); } } @@ -162,10 +165,12 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable { return this.comparators.size(); } + @Override + @SuppressWarnings("unchecked") public int compare(T o1, T o2) { Assert.state(this.comparators.size() > 0, "No sort definitions have been added to this CompoundComparator to compare"); - for (InvertibleComparator<T> comparator : this.comparators) { + for (InvertibleComparator comparator : this.comparators) { int result = comparator.compare(o1, o2); if (result != 0) { return result; diff --git a/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java index f29310e2..35d324ee 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java @@ -51,6 +51,7 @@ public class InstanceComparator<T> implements Comparator<T> { } + @Override public int compare(T o1, T o2) { int i1 = getOrder(o1); int i2 = getOrder(o2); diff --git a/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java index f78e56ff..9a9f5f9c 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java @@ -84,6 +84,7 @@ public class InvertibleComparator<T> implements Comparator<T>, Serializable { } + @Override public int compare(T o1, T o2) { int result = this.comparator.compare(o1, o2); if (result != 0) { diff --git a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java index 1171bdc1..f18b374d 100644 --- a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java +++ b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java @@ -64,7 +64,7 @@ public class NullSafeComparator<T> implements Comparator<T> { * @see #NULLS_LOW * @see #NULLS_HIGH */ - @SuppressWarnings({ "unchecked"}) + @SuppressWarnings({ "unchecked", "rawtypes"}) private NullSafeComparator(boolean nullsLow) { this.nonNullComparator = new ComparableComparator(); this.nullsLow = nullsLow; @@ -86,6 +86,7 @@ public class NullSafeComparator<T> implements Comparator<T> { } + @Override public int compare(T o1, T o2) { if (o1 == o2) { return 0; diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java new file mode 100644 index 00000000..b0c6e182 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util.concurrent; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.springframework.util.Assert; + +/** + * Abstract class that adapts a {@link Future} parameterized over S into a {@code + * Future} parameterized over T. All methods are delegated to the adaptee, where {@link + * #get()} and {@link #get(long, TimeUnit)} call {@link #adapt(Object)} on the adaptee's + * result. + * + * @author Arjen Poutsma + * @since 4.0 + * @param <T> the type of this {@code Future} + * @param <S> the type of the adaptee's {@code Future} + */ +public abstract class FutureAdapter<T, S> implements Future<T> { + + private final Future<S> adaptee; + + private Object result = null; + + private State state = State.NEW; + + private final Object mutex = new Object(); + + + /** + * Constructs a new {@code FutureAdapter} with the given adaptee. + * @param adaptee the future to delegate to + */ + protected FutureAdapter(Future<S> adaptee) { + Assert.notNull(adaptee, "'delegate' must not be null"); + this.adaptee = adaptee; + } + + + /** + * Returns the adaptee. + */ + protected Future<S> getAdaptee() { + return adaptee; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return adaptee.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return adaptee.isCancelled(); + } + + @Override + public boolean isDone() { + return adaptee.isDone(); + } + + @Override + public T get() throws InterruptedException, ExecutionException { + return adaptInternal(adaptee.get()); + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return adaptInternal(this.adaptee.get(timeout, unit)); + } + + @SuppressWarnings("unchecked") + final T adaptInternal(S adapteeResult) throws ExecutionException { + synchronized (this.mutex) { + switch (this.state) { + case SUCCESS: + return (T) this.result; + case FAILURE: + throw (ExecutionException) this.result; + case NEW: + try { + T adapted = adapt(adapteeResult); + this.result = adapted; + this.state = State.SUCCESS; + return adapted; + } + catch (ExecutionException ex) { + this.result = ex; + this.state = State.FAILURE; + throw ex; + } + default: + throw new IllegalStateException(); + } + } + } + + /** + * Adapts the given adaptee's result into T. + * @return the adapted result + */ + protected abstract T adapt(S adapteeResult) throws ExecutionException; + + + private enum State {NEW, SUCCESS, FAILURE} + +} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java new file mode 100644 index 00000000..f95b48c0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.util.concurrent; + +import java.util.concurrent.Future; + +/** + * Extends the {@link Future} interface with the capability to accept completion + * callbacks. If the future has already completed when the callback is added, the + * callback will be triggered immediately. + * <p>Inspired by {@code com.google.common.util.concurrent.ListenableFuture}. + + * @author Arjen Poutsma + * @since 4.0 + */ +public interface ListenableFuture<T> extends Future<T> { + + /** + * Registers the given callback to this {@code ListenableFuture}. The callback will + * be triggered when this {@code Future} is complete or, if it is already complete, + * immediately. + * @param callback the callback to register + */ + void addCallback(ListenableFutureCallback<? super T> callback); + +} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java new file mode 100644 index 00000000..804a2a90 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util.concurrent; + +import java.util.concurrent.ExecutionException; + +/** + * Abstract class that adapts a {@link ListenableFuture} parameterized over S into a + * {@code ListenableFuture} parameterized over T. All methods are delegated to the + * adaptee, where {@link #get()}, {@link #get(long, java.util.concurrent.TimeUnit)}, + * and {@link ListenableFutureCallback#onSuccess(Object)} call {@link #adapt(Object)} + * on the adaptee's result. + * + * @param <T> the type of this {@code Future} + * @param <S> the type of the adaptee's {@code Future} + * @author Arjen Poutsma + * @since 4.0 + */ +public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S> implements ListenableFuture<T> { + + /** + * Construct a new {@code ListenableFutureAdapter} with the given adaptee. + * @param adaptee the future to adapt to + */ + protected ListenableFutureAdapter(ListenableFuture<S> adaptee) { + super(adaptee); + } + + @Override + public void addCallback(final ListenableFutureCallback<? super T> callback) { + ListenableFuture<S> listenableAdaptee = (ListenableFuture<S>) getAdaptee(); + listenableAdaptee.addCallback(new ListenableFutureCallback<S>() { + @Override + public void onSuccess(S result) { + try { + callback.onSuccess(adaptInternal(result)); + } + catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + onFailure(cause != null ? cause : ex); + } + catch (Throwable ex) { + onFailure(ex); + } + } + @Override + public void onFailure(Throwable ex) { + callback.onFailure(ex); + } + }); + } + +} diff --git a/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java index bf5900fb..924c4439 100644 --- a/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java +++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,27 @@ * limitations under the License. */ -package org.springframework.asm.util; - -import java.io.PrintWriter; - -import org.springframework.asm.ClassVisitor; -import org.springframework.asm.SpringAsmInfo; +package org.springframework.util.concurrent; /** - * Dummy implementation of missing TraceClassVisitor from cglib-nodep's internally - * repackaged ASM library, added to avoid NoClassDefFoundErrors. + * Defines the contract for callbacks that accept the result of a + * {@link ListenableFuture}. * - * @author Chris Beams - * @since 3.2 + * @author Arjen Poutsma + * @since 4.0 */ -public class TraceClassVisitor extends ClassVisitor { +public interface ListenableFutureCallback<T> { + + /** + * Called when the {@link ListenableFuture} successfully completes. + * @param result the result + */ + void onSuccess(T result); - public TraceClassVisitor(Object object, PrintWriter pw) { - super(SpringAsmInfo.ASM_VERSION); - } + /** + * Called when the {@link ListenableFuture} fails to complete. + * @param ex the exception that triggered the failure + */ + void onFailure(Throwable ex); } diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java new file mode 100644 index 00000000..ec328f14 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util.concurrent; + +import java.util.LinkedList; +import java.util.Queue; + +import org.springframework.util.Assert; + +/** + * Registry for {@link ListenableFutureCallback} instances. + * + * <p>Inspired by {@code com.google.common.util.concurrent.ExecutionList}. + * + * @author Arjen Poutsma + * @since 4.0 + */ +public class ListenableFutureCallbackRegistry<T> { + + private final Queue<ListenableFutureCallback<? super T>> callbacks = + new LinkedList<ListenableFutureCallback<? super T>>(); + + private State state = State.NEW; + + private Object result = null; + + private final Object mutex = new Object(); + + + /** + * Add the given callback to this registry. + * @param callback the callback to add + */ + @SuppressWarnings("unchecked") + public void addCallback(ListenableFutureCallback<? super T> callback) { + Assert.notNull(callback, "'callback' must not be null"); + synchronized (this.mutex) { + switch (this.state) { + case NEW: + this.callbacks.add(callback); + break; + case SUCCESS: + callback.onSuccess((T) this.result); + break; + case FAILURE: + callback.onFailure((Throwable) this.result); + break; + } + } + } + + /** + * Trigger a {@link ListenableFutureCallback#onSuccess(Object)} call on all + * added callbacks with the given result. + * @param result the result to trigger the callbacks with + */ + public void success(T result) { + synchronized (this.mutex) { + this.state = State.SUCCESS; + this.result = result; + while (!this.callbacks.isEmpty()) { + this.callbacks.poll().onSuccess(result); + } + } + } + + /** + * Trigger a {@link ListenableFutureCallback#onFailure(Throwable)} call on all + * added callbacks with the given {@code Throwable}. + * @param ex the exception to trigger the callbacks with + */ + public void failure(Throwable ex) { + synchronized (this.mutex) { + this.state = State.FAILURE; + this.result = ex; + while (!this.callbacks.isEmpty()) { + this.callbacks.poll().onFailure(ex); + } + } + } + + + private enum State {NEW, SUCCESS, FAILURE} + +} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java new file mode 100644 index 00000000..b61f885f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * 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 org.springframework.util.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Extension of {@link FutureTask} that implements {@link ListenableFuture}. + * + * @author Arjen Poutsma + * @since 4.0 + */ +public class ListenableFutureTask<T> extends FutureTask<T> implements ListenableFuture<T> { + + private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<T>(); + + + /** + * Create a new {@code ListenableFutureTask} that will, upon running, + * execute the given {@link Callable}. + * @param callable the callable task + */ + public ListenableFutureTask(Callable<T> callable) { + super(callable); + } + + /** + * Create a {@code ListenableFutureTask} that will, upon running, + * execute the given {@link Runnable}, and arrange that {@link #get()} + * will return the given result on successful completion. + * @param runnable the runnable task + * @param result the result to return on successful completion + */ + public ListenableFutureTask(Runnable runnable, T result) { + super(runnable, result); + } + + + @Override + public void addCallback(ListenableFutureCallback<? super T> callback) { + this.callbacks.addCallback(callback); + } + + @Override + protected final void done() { + Throwable cause; + try { + T result = get(); + this.callbacks.success(result); + return; + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return; + } + catch (ExecutionException ex) { + cause = ex.getCause(); + if (cause == null) { + cause = ex; + } + } + catch (Throwable ex) { + cause = ex; + } + this.callbacks.failure(cause); + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java b/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java new file mode 100644 index 00000000..ce15f2eb --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java @@ -0,0 +1,8 @@ + +/** + * + * Useful generic {@code java.util.concurrent.Future} extension. + * + */ +package org.springframework.util.concurrent; + diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java deleted file mode 100644 index 46f4d98e..00000000 --- a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.util.xml; - -import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; - -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; - -/** - * Abstract base class for SAX {@code ContentHandler} implementations that use StAX as a basis. All methods - * delegate to internal template methods, capable of throwing a {@code XMLStreamException}. Additionally, an - * namespace context is used to keep track of declared namespaces. - * - * @author Arjen Poutsma - * @since 3.0 - */ -abstract class AbstractStaxContentHandler implements ContentHandler { - - private SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); - - private boolean namespaceContextChanged = false; - - public final void startDocument() throws SAXException { - namespaceContext.clear(); - namespaceContextChanged = false; - try { - startDocumentInternal(); - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); - } - } - - protected abstract void startDocumentInternal() throws XMLStreamException; - - public final void endDocument() throws SAXException { - namespaceContext.clear(); - namespaceContextChanged = false; - try { - endDocumentInternal(); - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); - } - } - - protected abstract void endDocumentInternal() throws XMLStreamException; - - /** - * Binds the given prefix to the given namespaces. - * - * @see SimpleNamespaceContext#bindNamespaceUri(String,String) - */ - public final void startPrefixMapping(String prefix, String uri) { - namespaceContext.bindNamespaceUri(prefix, uri); - namespaceContextChanged = true; - } - - /** - * Removes the binding for the given prefix. - * - * @see SimpleNamespaceContext#removeBinding(String) - */ - public final void endPrefixMapping(String prefix) { - namespaceContext.removeBinding(prefix); - namespaceContextChanged = true; - } - - public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { - try { - startElementInternal(toQName(uri, qName), atts, namespaceContextChanged ? namespaceContext : null); - namespaceContextChanged = false; - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle startElement: " + ex.getMessage(), ex); - } - } - - protected abstract void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext) - throws XMLStreamException; - - public final void endElement(String uri, String localName, String qName) throws SAXException { - try { - endElementInternal(toQName(uri, qName), namespaceContextChanged ? namespaceContext : null); - namespaceContextChanged = false; - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle endElement: " + ex.getMessage(), ex); - } - } - - protected abstract void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) - throws XMLStreamException; - - public final void characters(char ch[], int start, int length) throws SAXException { - try { - charactersInternal(ch, start, length); - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle characters: " + ex.getMessage(), ex); - } - } - - protected abstract void charactersInternal(char[] ch, int start, int length) throws XMLStreamException; - - public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { - try { - ignorableWhitespaceInternal(ch, start, length); - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle ignorableWhitespace:" + ex.getMessage(), ex); - } - } - - protected abstract void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException; - - public final void processingInstruction(String target, String data) throws SAXException { - try { - processingInstructionInternal(target, data); - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle processingInstruction: " + ex.getMessage(), ex); - } - } - - protected abstract void processingInstructionInternal(String target, String data) throws XMLStreamException; - - public final void skippedEntity(String name) throws SAXException { - try { - skippedEntityInternal(name); - } - catch (XMLStreamException ex) { - throw new SAXException("Could not handle skippedEntity: " + ex.getMessage(), ex); - } - } - - /** - * Convert a namespace URI and DOM or SAX qualified name to a {@code QName}. The qualified name can have the form - * {@code prefix:localname} or {@code localName}. - * - * @param namespaceUri the namespace URI - * @param qualifiedName the qualified name - * @return a QName - */ - protected QName toQName(String namespaceUri, String qualifiedName) { - int idx = qualifiedName.indexOf(':'); - if (idx == -1) { - return new QName(namespaceUri, qualifiedName); - } - else { - String prefix = qualifiedName.substring(0, idx); - String localPart = qualifiedName.substring(idx + 1); - return new QName(namespaceUri, localPart, prefix); - } - } - - protected abstract void skippedEntityInternal(String name) throws XMLStreamException; -} diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxHandler.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxHandler.java new file mode 100644 index 00000000..afbdf9a0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxHandler.java @@ -0,0 +1,273 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util.xml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + +/** + * Abstract base class for SAX {@code ContentHandler} and {@code LexicalHandler} + * implementations that use StAX as a basis. All methods delegate to internal template + * methods, capable of throwing a {@code XMLStreamException}. Additionally, an namespace + * context stack is used to keep track of declared namespaces. + * + * @author Arjen Poutsma + * @since 4.0.3 + */ +abstract class AbstractStaxHandler implements ContentHandler, LexicalHandler { + + private final List<Map<String, String>> namespaceMappings = new ArrayList<Map<String, String>>(); + + private boolean inCData; + + + @Override + public final void startDocument() throws SAXException { + removeAllNamespaceMappings(); + newNamespaceMapping(); + try { + startDocumentInternal(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); + } + } + + @Override + public final void endDocument() throws SAXException { + removeAllNamespaceMappings(); + try { + endDocumentInternal(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle endDocument: " + ex.getMessage(), ex); + } + } + + @Override + public final void startPrefixMapping(String prefix, String uri) { + currentNamespaceMapping().put(prefix, uri); + } + + @Override + public final void endPrefixMapping(String prefix) { + } + + @Override + public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + try { + startElementInternal(toQName(uri, qName), atts, currentNamespaceMapping()); + newNamespaceMapping(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startElement: " + ex.getMessage(), ex); + } + } + + @Override + public final void endElement(String uri, String localName, String qName) throws SAXException { + try { + endElementInternal(toQName(uri, qName), currentNamespaceMapping()); + removeNamespaceMapping(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle endElement: " + ex.getMessage(), ex); + } + } + + @Override + public final void characters(char ch[], int start, int length) throws SAXException { + try { + String data = new String(ch, start, length); + if (!this.inCData) { + charactersInternal(data); + } + else { + cDataInternal(data); + } + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle characters: " + ex.getMessage(), ex); + } + } + + @Override + public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + try { + ignorableWhitespaceInternal(new String(ch, start, length)); + } + catch (XMLStreamException ex) { + throw new SAXException( + "Could not handle ignorableWhitespace:" + ex.getMessage(), ex); + } + } + + @Override + public final void processingInstruction(String target, String data) throws SAXException { + try { + processingInstructionInternal(target, data); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle processingInstruction: " + ex.getMessage(), ex); + } + } + + @Override + public final void skippedEntity(String name) throws SAXException { + try { + skippedEntityInternal(name); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle skippedEntity: " + ex.getMessage(), ex); + } + } + + @Override + public final void startDTD(String name, String publicId, String systemId) throws SAXException { + try { + StringBuilder builder = new StringBuilder("<!DOCTYPE "); + builder.append(name); + if (publicId != null) { + builder.append(" PUBLIC \""); + builder.append(publicId); + builder.append("\" \""); + } + else { + builder.append(" SYSTEM \""); + } + builder.append(systemId); + builder.append("\">"); + + dtdInternal(builder.toString()); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startDTD: " + ex.getMessage(), ex); + } + } + + @Override + public final void endDTD() throws SAXException { + } + + @Override + public final void startCDATA() throws SAXException { + this.inCData = true; + } + + @Override + public final void endCDATA() throws SAXException { + this.inCData = false; + } + + @Override + public final void comment(char[] ch, int start, int length) throws SAXException { + try { + commentInternal(new String(ch, start, length)); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle comment: " + ex.getMessage(), ex); + } + } + + @Override + public void startEntity(String name) throws SAXException { + } + + @Override + public void endEntity(String name) throws SAXException { + } + + /** + * Convert a namespace URI and DOM or SAX qualified name to a {@code QName}. The + * qualified name can have the form {@code prefix:localname} or {@code localName}. + * @param namespaceUri the namespace URI + * @param qualifiedName the qualified name + * @return a QName + */ + protected QName toQName(String namespaceUri, String qualifiedName) { + int idx = qualifiedName.indexOf(':'); + if (idx == -1) { + return new QName(namespaceUri, qualifiedName); + } + else { + String prefix = qualifiedName.substring(0, idx); + String localPart = qualifiedName.substring(idx + 1); + return new QName(namespaceUri, localPart, prefix); + } + } + + protected boolean isNamespaceDeclaration(QName qName) { + String prefix = qName.getPrefix(); + String localPart = qName.getLocalPart(); + return (XMLConstants.XMLNS_ATTRIBUTE.equals(localPart) && prefix.length() == 0) || + (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) && localPart.length() != 0); + } + + + private Map<String, String> currentNamespaceMapping() { + return this.namespaceMappings.get(this.namespaceMappings.size() - 1); + } + + private void newNamespaceMapping() { + this.namespaceMappings.add(new HashMap<String, String>()); + } + + private void removeNamespaceMapping() { + this.namespaceMappings.remove(this.namespaceMappings.size() - 1); + } + + private void removeAllNamespaceMappings() { + this.namespaceMappings.clear(); + } + + + protected abstract void startDocumentInternal() throws XMLStreamException; + + protected abstract void endDocumentInternal() throws XMLStreamException; + + protected abstract void startElementInternal(QName name, Attributes attributes, + Map<String, String> namespaceMapping) throws XMLStreamException; + + protected abstract void endElementInternal(QName name, Map<String, String> namespaceMapping) + throws XMLStreamException; + + protected abstract void charactersInternal(String data) throws XMLStreamException; + + protected abstract void cDataInternal(String data) throws XMLStreamException; + + protected abstract void ignorableWhitespaceInternal(String data) throws XMLStreamException; + + protected abstract void processingInstructionInternal(String target, String data) + throws XMLStreamException; + + protected abstract void skippedEntityInternal(String name) throws XMLStreamException; + + protected abstract void dtdInternal(String dtd) throws XMLStreamException; + + protected abstract void commentInternal(String comment) throws XMLStreamException; + +} diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java index feeefd6e..25dc97b2 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java @@ -134,6 +134,7 @@ abstract class AbstractStaxXMLReader extends AbstractXMLReader { * @param ignored is ignored * @throws SAXException a SAX exception, possibly wrapping a {@code XMLStreamException} */ + @Override public final void parse(InputSource ignored) throws SAXException { parse(); } @@ -144,6 +145,7 @@ abstract class AbstractStaxXMLReader extends AbstractXMLReader { * @param ignored is ignored * @throws SAXException A SAX exception, possibly wrapping a {@code XMLStreamException} */ + @Override public final void parse(String ignored) throws SAXException { parse(); } @@ -217,18 +219,22 @@ abstract class AbstractStaxXMLReader extends AbstractXMLReader { this.location = location; } + @Override public String getPublicId() { return location.getPublicId(); } + @Override public String getSystemId() { return location.getSystemId(); } + @Override public int getLineNumber() { return location.getLineNumber(); } + @Override public int getColumnNumber() { return location.getColumnNumber(); } diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java index 300f9ff2..7b24a3c1 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java @@ -48,34 +48,42 @@ abstract class AbstractXMLReader implements XMLReader { private LexicalHandler lexicalHandler; + @Override public ContentHandler getContentHandler() { return contentHandler; } + @Override public void setContentHandler(ContentHandler contentHandler) { this.contentHandler = contentHandler; } + @Override public void setDTDHandler(DTDHandler dtdHandler) { this.dtdHandler = dtdHandler; } + @Override public DTDHandler getDTDHandler() { return dtdHandler; } + @Override public EntityResolver getEntityResolver() { return entityResolver; } + @Override public void setEntityResolver(EntityResolver entityResolver) { this.entityResolver = entityResolver; } + @Override public ErrorHandler getErrorHandler() { return errorHandler; } + @Override public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } @@ -90,6 +98,7 @@ abstract class AbstractXMLReader implements XMLReader { * @throws org.xml.sax.SAXNotRecognizedException * always */ + @Override public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { throw new SAXNotRecognizedException(name); } @@ -99,6 +108,7 @@ abstract class AbstractXMLReader implements XMLReader { * * @throws SAXNotRecognizedException always */ + @Override public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { throw new SAXNotRecognizedException(name); } @@ -107,6 +117,7 @@ abstract class AbstractXMLReader implements XMLReader { * Throws a {@code SAXNotRecognizedException} exception when the given property does not signify a lexical * handler. The property name for a lexical handler is {@code http://xml.org/sax/properties/lexical-handler}. */ + @Override public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { if ("http://xml.org/sax/properties/lexical-handler".equals(name)) { return lexicalHandler; @@ -120,6 +131,7 @@ abstract class AbstractXMLReader implements XMLReader { * Throws a {@code SAXNotRecognizedException} exception when the given property does not signify a lexical * handler. The property name for a lexical handler is {@code http://xml.org/sax/properties/lexical-handler}. */ + @Override public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { if ("http://xml.org/sax/properties/lexical-handler".equals(name)) { lexicalHandler = (LexicalHandler) value; diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java index e8c8a643..6335ea15 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java @@ -31,6 +31,7 @@ import org.springframework.util.Assert; */ abstract class AbstractXMLStreamReader implements XMLStreamReader { + @Override public String getElementText() throws XMLStreamException { if (getEventType() != XMLStreamConstants.START_ELEMENT) { throw new XMLStreamException("parser must be on START_ELEMENT to read next text", getLocation()); @@ -61,18 +62,22 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { return builder.toString(); } + @Override public String getAttributeLocalName(int index) { return getAttributeName(index).getLocalPart(); } + @Override public String getAttributeNamespace(int index) { return getAttributeName(index).getNamespaceURI(); } + @Override public String getAttributePrefix(int index) { return getAttributeName(index).getPrefix(); } + @Override public String getNamespaceURI() { int eventType = getEventType(); if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { @@ -83,11 +88,13 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { } } + @Override public String getNamespaceURI(String prefix) { Assert.notNull(prefix, "No prefix given"); return getNamespaceContext().getNamespaceURI(prefix); } + @Override public boolean hasText() { int eventType = getEventType(); return eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.CHARACTERS || @@ -95,6 +102,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { eventType == XMLStreamConstants.ENTITY_REFERENCE; } + @Override public String getPrefix() { int eventType = getEventType(); if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { @@ -105,27 +113,33 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { } } + @Override public boolean hasName() { int eventType = getEventType(); return eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT; } + @Override public boolean isWhiteSpace() { return getEventType() == XMLStreamConstants.SPACE; } + @Override public boolean isStartElement() { return getEventType() == XMLStreamConstants.START_ELEMENT; } + @Override public boolean isEndElement() { return getEventType() == XMLStreamConstants.END_ELEMENT; } + @Override public boolean isCharacters() { return getEventType() == XMLStreamConstants.CHARACTERS; } + @Override public int nextTag() throws XMLStreamException { int eventType = next(); while (eventType == XMLStreamConstants.CHARACTERS && isWhiteSpace() || @@ -139,6 +153,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { return eventType; } + @Override public void require(int expectedType, String namespaceURI, String localName) throws XMLStreamException { int eventType = getEventType(); if (eventType != expectedType) { @@ -146,6 +161,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { } } + @Override public String getAttributeValue(String namespaceURI, String localName) { for (int i = 0; i < getAttributeCount(); i++) { QName name = getAttributeName(i); @@ -157,18 +173,22 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { return null; } + @Override public boolean hasNext() throws XMLStreamException { return getEventType() != END_DOCUMENT; } + @Override public String getLocalName() { return getName().getLocalPart(); } + @Override public char[] getTextCharacters() { return getText().toCharArray(); } + @Override public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException { char[] source = getTextCharacters(); @@ -177,6 +197,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader { return length; } + @Override public int getTextLength() { return getText().length(); } diff --git a/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java index 534cb007..5a8d92aa 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java +++ b/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java @@ -72,6 +72,7 @@ class DomContentHandler implements ContentHandler { } } + @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { Node parent = getParent(); Element element = document.createElementNS(uri, qName); @@ -87,10 +88,12 @@ class DomContentHandler implements ContentHandler { elements.add(element); } + @Override public void endElement(String uri, String localName, String qName) throws SAXException { elements.remove(elements.size() - 1); } + @Override public void characters(char ch[], int start, int length) throws SAXException { String data = new String(ch, start, length); Node parent = getParent(); @@ -104,6 +107,7 @@ class DomContentHandler implements ContentHandler { } } + @Override public void processingInstruction(String target, String data) throws SAXException { Node parent = getParent(); ProcessingInstruction pi = document.createProcessingInstruction(target, data); @@ -114,24 +118,31 @@ class DomContentHandler implements ContentHandler { * Unsupported */ + @Override public void setDocumentLocator(Locator locator) { } + @Override public void startDocument() throws SAXException { } + @Override public void endDocument() throws SAXException { } + @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { } + @Override public void endPrefixMapping(String prefix) throws SAXException { } + @Override public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { } + @Override public void skippedEntity(String name) throws SAXException { } } diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java index c0d966d6..f865c956 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java +++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java @@ -43,6 +43,7 @@ public class SimpleNamespaceContext implements NamespaceContext { private String defaultNamespaceUri = ""; + @Override public String getNamespaceURI(String prefix) { Assert.notNull(prefix, "prefix is null"); if (XMLConstants.XML_NS_PREFIX.equals(prefix)) { @@ -60,12 +61,14 @@ public class SimpleNamespaceContext implements NamespaceContext { return ""; } + @Override public String getPrefix(String namespaceUri) { - List prefixes = getPrefixesInternal(namespaceUri); + List<?> prefixes = getPrefixesInternal(namespaceUri); return prefixes.isEmpty() ? null : (String) prefixes.get(0); } - public Iterator getPrefixes(String namespaceUri) { + @Override + public Iterator<String> getPrefixes(String namespaceUri) { return getPrefixesInternal(namespaceUri).iterator(); } @@ -152,7 +155,7 @@ public class SimpleNamespaceContext implements NamespaceContext { } else { String namespaceUri = prefixToNamespaceUri.remove(prefix); - List prefixes = getPrefixesInternal(namespaceUri); + List<String> prefixes = getPrefixesInternal(namespaceUri); prefixes.remove(prefix); } } diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java index ed06ed8c..3d0227db 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java +++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java @@ -43,14 +43,17 @@ public class SimpleSaxErrorHandler implements ErrorHandler { } + @Override public void warning(SAXParseException ex) throws SAXException { logger.warn("Ignored XML validation warning", ex); } + @Override public void error(SAXParseException ex) throws SAXException { throw ex; } + @Override public void fatalError(SAXParseException ex) throws SAXException { throw ex; } diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java index 7fd5dffa..cab7670a 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java +++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java @@ -43,14 +43,17 @@ public class SimpleTransformErrorListener implements ErrorListener { } + @Override public void warning(TransformerException ex) throws TransformerException { logger.warn("XSLT transformation warning", ex); } + @Override public void error(TransformerException ex) throws TransformerException { logger.error("XSLT transformation error", ex); } + @Override public void fatalError(TransformerException ex) throws TransformerException { throw ex; } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java deleted file mode 100644 index 686ff1a6..00000000 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.util.xml; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import javax.xml.XMLConstants; -import javax.xml.namespace.QName; -import javax.xml.stream.Location; -import javax.xml.stream.XMLEventFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Attribute; -import javax.xml.stream.events.Namespace; -import javax.xml.stream.events.XMLEvent; -import javax.xml.stream.util.XMLEventConsumer; - -import org.xml.sax.Attributes; -import org.xml.sax.Locator; - -import org.springframework.util.StringUtils; - -/** - * SAX {@code ContentHandler} that transforms callback calls to {@code XMLEvent}s - * and writes them to a {@code XMLEventConsumer}. - * - * @author Arjen Poutsma - * @since 3.0 - * @see XMLEvent - * @see XMLEventConsumer - */ -class StaxEventContentHandler extends AbstractStaxContentHandler { - - private final XMLEventFactory eventFactory; - - private final XMLEventConsumer eventConsumer; - - - /** - * Construct a new instance of the {@code StaxEventContentHandler} that writes to the given - * {@code XMLEventConsumer}. A default {@code XMLEventFactory} will be created. - * @param consumer the consumer to write events to - */ - StaxEventContentHandler(XMLEventConsumer consumer) { - this.eventFactory = XMLEventFactory.newInstance(); - this.eventConsumer = consumer; - } - - /** - * Construct a new instance of the {@code StaxEventContentHandler} that uses the given - * event factory to create events and writes to the given {@code XMLEventConsumer}. - * @param consumer the consumer to write events to - * @param factory the factory used to create events - */ - StaxEventContentHandler(XMLEventConsumer consumer, XMLEventFactory factory) { - this.eventFactory = factory; - this.eventConsumer = consumer; - } - - public void setDocumentLocator(Locator locator) { - if (locator != null) { - this.eventFactory.setLocation(new LocatorLocationAdapter(locator)); - } - } - - @Override - protected void startDocumentInternal() throws XMLStreamException { - consumeEvent(this.eventFactory.createStartDocument()); - } - - @Override - protected void endDocumentInternal() throws XMLStreamException { - consumeEvent(this.eventFactory.createEndDocument()); - } - - @Override - protected void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext) - throws XMLStreamException { - - List attributes = getAttributes(atts); - List namespaces = createNamespaces(namespaceContext); - consumeEvent(this.eventFactory.createStartElement(name, attributes.iterator(), - (namespaces != null ? namespaces.iterator() : null))); - } - - @Override - protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException { - List namespaces = createNamespaces(namespaceContext); - consumeEvent(this.eventFactory.createEndElement(name, namespaces != null ? namespaces.iterator() : null)); - } - - @Override - protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException { - consumeEvent(this.eventFactory.createCharacters(new String(ch, start, length))); - } - - @Override - protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException { - consumeEvent(this.eventFactory.createIgnorableSpace(new String(ch, start, length))); - } - - @Override - protected void processingInstructionInternal(String target, String data) throws XMLStreamException { - consumeEvent(this.eventFactory.createProcessingInstruction(target, data)); - } - - private void consumeEvent(XMLEvent event) throws XMLStreamException { - this.eventConsumer.add(event); - } - - /** - * Create and return a list of {@code NameSpace} objects from the {@code NamespaceContext}. - */ - private List<Namespace> createNamespaces(SimpleNamespaceContext namespaceContext) { - if (namespaceContext == null) { - return null; - } - - List<Namespace> namespaces = new ArrayList<Namespace>(); - String defaultNamespaceUri = namespaceContext.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX); - if (StringUtils.hasLength(defaultNamespaceUri)) { - namespaces.add(this.eventFactory.createNamespace(defaultNamespaceUri)); - } - for (Iterator iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) { - String prefix = (String) iterator.next(); - String namespaceUri = namespaceContext.getNamespaceURI(prefix); - namespaces.add(this.eventFactory.createNamespace(prefix, namespaceUri)); - } - return namespaces; - } - - private List<Attribute> getAttributes(Attributes attributes) { - List<Attribute> list = new ArrayList<Attribute>(); - for (int i = 0; i < attributes.getLength(); i++) { - QName name = toQName(attributes.getURI(i), attributes.getQName(i)); - if (!("xmlns".equals(name.getLocalPart()) || "xmlns".equals(name.getPrefix()))) { - list.add(this.eventFactory.createAttribute(name, attributes.getValue(i))); - } - } - return list; - } - - /* No operation */ - @Override - protected void skippedEntityInternal(String name) throws XMLStreamException { - } - - - private static final class LocatorLocationAdapter implements Location { - - private final Locator locator; - - public LocatorLocationAdapter(Locator locator) { - this.locator = locator; - } - - public int getLineNumber() { - return this.locator.getLineNumber(); - } - - public int getColumnNumber() { - return this.locator.getColumnNumber(); - } - - public int getCharacterOffset() { - return -1; - } - - public String getPublicId() { - return this.locator.getPublicId(); - } - - public String getSystemId() { - return this.locator.getSystemId(); - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxEventHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventHandler.java new file mode 100644 index 00000000..7a4151dc --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventHandler.java @@ -0,0 +1,196 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util.xml; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Namespace; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.ext.LexicalHandler; + +/** + * SAX {@link org.xml.sax.ContentHandler} and {@link LexicalHandler} + * that writes to a {@link javax.xml.stream.util.XMLEventConsumer}. + * + * @author Arjen Poutsma + * @since 4.0.3 + */ +class StaxEventHandler extends AbstractStaxHandler { + + private final XMLEventFactory eventFactory; + + private final XMLEventWriter eventWriter; + + + /** + * Construct a new instance of the {@code StaxEventContentHandler} that writes to the + * given {@code XMLEventWriter}. A default {@code XMLEventFactory} will be created. + * @param eventWriter the writer to write events to + */ + public StaxEventHandler(XMLEventWriter eventWriter) { + this.eventFactory = XMLEventFactory.newInstance(); + this.eventWriter = eventWriter; + } + + /** + * Construct a new instance of the {@code StaxEventContentHandler} that uses the given + * event factory to create events and writes to the given {@code XMLEventConsumer}. + * @param eventWriter the writer to write events to + * @param factory the factory used to create events + */ + public StaxEventHandler(XMLEventWriter eventWriter, XMLEventFactory factory) { + this.eventFactory = factory; + this.eventWriter = eventWriter; + } + + + @Override + public void setDocumentLocator(Locator locator) { + if (locator != null) { + this.eventFactory.setLocation(new LocatorLocationAdapter(locator)); + } + } + + @Override + protected void startDocumentInternal() throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createStartDocument()); + } + + @Override + protected void endDocumentInternal() throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createEndDocument()); + } + + @Override + protected void startElementInternal(QName name, Attributes atts, + Map<String, String> namespaceMapping) throws XMLStreamException { + + List<Attribute> attributes = getAttributes(atts); + List<Namespace> namespaces = getNamespaces(namespaceMapping); + this.eventWriter.add( + this.eventFactory.createStartElement(name, attributes.iterator(), namespaces.iterator())); + + } + + private List<Namespace> getNamespaces(Map<String, String> namespaceMapping) { + List<Namespace> result = new ArrayList<Namespace>(); + for (Map.Entry<String, String> entry : namespaceMapping.entrySet()) { + String prefix = entry.getKey(); + String namespaceUri = entry.getValue(); + result.add(this.eventFactory.createNamespace(prefix, namespaceUri)); + } + return result; + } + + private List<Attribute> getAttributes(Attributes attributes) { + List<Attribute> result = new ArrayList<Attribute>(); + for (int i = 0; i < attributes.getLength(); i++) { + QName attrName = toQName(attributes.getURI(i), attributes.getQName(i)); + if (!isNamespaceDeclaration(attrName)) { + result.add(this.eventFactory.createAttribute(attrName, attributes.getValue(i))); + } + } + return result; + } + + @Override + protected void endElementInternal(QName name, Map<String, String> namespaceMapping) throws XMLStreamException { + List<Namespace> namespaces = getNamespaces(namespaceMapping); + this.eventWriter.add(this.eventFactory.createEndElement(name, namespaces.iterator())); + } + + @Override + protected void charactersInternal(String data) throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createCharacters(data)); + } + + @Override + protected void cDataInternal(String data) throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createCData(data)); + } + + @Override + protected void ignorableWhitespaceInternal(String data) throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createIgnorableSpace(data)); + } + + @Override + protected void processingInstructionInternal(String target, String data) throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, data)); + } + + @Override + protected void dtdInternal(String dtd) throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createDTD(dtd)); + } + + @Override + protected void commentInternal(String comment) throws XMLStreamException { + this.eventWriter.add(this.eventFactory.createComment(comment)); + } + + // Ignored + + @Override + protected void skippedEntityInternal(String name) throws XMLStreamException { + } + + + private static final class LocatorLocationAdapter implements Location { + + private final Locator locator; + + public LocatorLocationAdapter(Locator locator) { + this.locator = locator; + } + + @Override + public int getLineNumber() { + return this.locator.getLineNumber(); + } + + @Override + public int getColumnNumber() { + return this.locator.getColumnNumber(); + } + + @Override + public int getCharacterOffset() { + return -1; + } + + @Override + public String getPublicId() { + return this.locator.getPublicId(); + } + + @Override + public String getSystemId() { + return this.locator.getSystemId(); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java index fd43354a..490b9de8 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java @@ -17,8 +17,7 @@ package org.springframework.util.xml; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; + import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.XMLEventReader; @@ -38,14 +37,13 @@ import javax.xml.stream.events.StartDocument; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.ext.Locator2; import org.xml.sax.helpers.AttributesImpl; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - /** * SAX {@code XMLReader} that reads from a StAX {@code XMLEventReader}. Consumes {@code XMLEvents} from * an {@code XMLEventReader}, and calls the corresponding methods on the SAX callback interfaces. @@ -58,14 +56,13 @@ import org.springframework.util.StringUtils; * @see #setEntityResolver(org.xml.sax.EntityResolver) * @see #setErrorHandler(org.xml.sax.ErrorHandler) */ +@SuppressWarnings("rawtypes") class StaxEventXMLReader extends AbstractStaxXMLReader { private static final String DEFAULT_XML_VERSION = "1.0"; private final XMLEventReader reader; - private final Map<String, String> namespaces = new LinkedHashMap<String, String>(); - private String xmlVersion = DEFAULT_XML_VERSION; private String encoding; @@ -168,21 +165,27 @@ class StaxEventXMLReader extends AbstractStaxXMLReader { if (getContentHandler() != null) { final Location location = event.getLocation(); getContentHandler().setDocumentLocator(new Locator2() { + @Override public int getColumnNumber() { return (location != null ? location.getColumnNumber() : -1); } + @Override public int getLineNumber() { return (location != null ? location.getLineNumber() : -1); } + @Override public String getPublicId() { return (location != null ? location.getPublicId() : null); } + @Override public String getSystemId() { return (location != null ? location.getSystemId() : null); } + @Override public String getXMLVersion() { return xmlVersion; } + @Override public String getEncoding() { return encoding; } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java index 801769cd..ef91ceae 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.sax.SAXResult; import org.xml.sax.ContentHandler; +import org.xml.sax.ext.LexicalHandler; /** * Implementation of the {@code Result} tagging interface for StAX writers. Can be constructed with @@ -35,7 +36,8 @@ import org.xml.sax.ContentHandler; * {@code SAXResult} is <strong>not supported</strong>. In general, the only supported operation * on this class is to use the {@code ContentHandler} obtained via {@link #getHandler()} to parse an * input source using an {@code XMLReader}. Calling {@link #setHandler(org.xml.sax.ContentHandler)} - * will result in {@code UnsupportedOperationException}s. + * or {@link #setLexicalHandler(org.xml.sax.ext.LexicalHandler)} will result in + * {@code UnsupportedOperationException}s. * * @author Arjen Poutsma * @since 3.0 @@ -54,8 +56,10 @@ class StaxResult extends SAXResult { * Construct a new instance of the {@code StaxResult} with the specified {@code XMLStreamWriter}. * @param streamWriter the {@code XMLStreamWriter} to write to */ - StaxResult(XMLStreamWriter streamWriter) { - super.setHandler(new StaxStreamContentHandler(streamWriter)); + public StaxResult(XMLStreamWriter streamWriter) { + StaxStreamHandler handler = new StaxStreamHandler(streamWriter); + super.setHandler(handler); + super.setLexicalHandler(handler); this.streamWriter = streamWriter; } @@ -63,19 +67,10 @@ class StaxResult extends SAXResult { * Construct a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter}. * @param eventWriter the {@code XMLEventWriter} to write to */ - StaxResult(XMLEventWriter eventWriter) { - super.setHandler(new StaxEventContentHandler(eventWriter)); - this.eventWriter = eventWriter; - } - - /** - * Construct a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter} - * and {@code XMLEventFactory}. - * @param eventWriter the {@code XMLEventWriter} to write to - * @param eventFactory the {@code XMLEventFactory} to use for creating events - */ - StaxResult(XMLEventWriter eventWriter, XMLEventFactory eventFactory) { - super.setHandler(new StaxEventContentHandler(eventWriter, eventFactory)); + public StaxResult(XMLEventWriter eventWriter) { + StaxEventHandler handler = new StaxEventHandler(eventWriter); + super.setHandler(handler); + super.setLexicalHandler(handler); this.eventWriter = eventWriter; } @@ -86,7 +81,7 @@ class StaxResult extends SAXResult { * @return the StAX event writer used by this result * @see #StaxResult(javax.xml.stream.XMLEventWriter) */ - XMLEventWriter getXMLEventWriter() { + public XMLEventWriter getXMLEventWriter() { return this.eventWriter; } @@ -96,7 +91,7 @@ class StaxResult extends SAXResult { * @return the StAX stream writer used by this result * @see #StaxResult(javax.xml.stream.XMLStreamWriter) */ - XMLStreamWriter getXMLStreamWriter() { + public XMLStreamWriter getXMLStreamWriter() { return this.streamWriter; } @@ -110,4 +105,13 @@ class StaxResult extends SAXResult { throw new UnsupportedOperationException("setHandler is not supported"); } + /** + * Throws an {@code UnsupportedOperationException}. + * @throws UnsupportedOperationException always + */ + @Override + public void setLexicalHandler(LexicalHandler handler) { + throw new UnsupportedOperationException("setLexicalHandler is not supported"); + } + } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java deleted file mode 100644 index 2241e2f2..00000000 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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 org.springframework.util.xml; - -import java.util.Iterator; -import javax.xml.namespace.QName; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -import org.xml.sax.Attributes; -import org.xml.sax.Locator; - -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * SAX {@code ContentHandler} that writes to a {@code XMLStreamWriter}. - * - * @author Arjen Poutsma - * @see XMLStreamWriter - * @since 3.0 - */ -class StaxStreamContentHandler extends AbstractStaxContentHandler { - - private final XMLStreamWriter streamWriter; - - /** - * Constructs a new instance of the {@code StaxStreamContentHandler} that writes to the given - * {@code XMLStreamWriter}. - * - * @param streamWriter the stream writer to write to - */ - StaxStreamContentHandler(XMLStreamWriter streamWriter) { - Assert.notNull(streamWriter, "'streamWriter' must not be null"); - this.streamWriter = streamWriter; - } - - public void setDocumentLocator(Locator locator) { - } - - @Override - protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException { - streamWriter.writeCharacters(ch, start, length); - } - - @Override - protected void endDocumentInternal() throws XMLStreamException { - streamWriter.writeEndDocument(); - } - - @Override - protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException { - streamWriter.writeEndElement(); - } - - @Override - protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException { - streamWriter.writeCharacters(ch, start, length); - } - - @Override - protected void processingInstructionInternal(String target, String data) throws XMLStreamException { - streamWriter.writeProcessingInstruction(target, data); - } - - @Override - protected void skippedEntityInternal(String name) { - } - - @Override - protected void startDocumentInternal() throws XMLStreamException { - streamWriter.writeStartDocument(); - } - - @Override - protected void startElementInternal(QName name, Attributes attributes, SimpleNamespaceContext namespaceContext) - throws XMLStreamException { - streamWriter.writeStartElement(name.getPrefix(), name.getLocalPart(), name.getNamespaceURI()); - if (namespaceContext != null) { - String defaultNamespaceUri = namespaceContext.getNamespaceURI(""); - if (StringUtils.hasLength(defaultNamespaceUri)) { - streamWriter.writeNamespace("", defaultNamespaceUri); - streamWriter.setDefaultNamespace(defaultNamespaceUri); - } - for (Iterator<String> iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) { - String prefix = iterator.next(); - streamWriter.writeNamespace(prefix, namespaceContext.getNamespaceURI(prefix)); - streamWriter.setPrefix(prefix, namespaceContext.getNamespaceURI(prefix)); - } - } - for (int i = 0; i < attributes.getLength(); i++) { - QName attrName = toQName(attributes.getURI(i), attributes.getQName(i)); - if (!("xmlns".equals(attrName.getLocalPart()) || "xmlns".equals(attrName.getPrefix()))) { - streamWriter.writeAttribute(attrName.getPrefix(), attrName.getNamespaceURI(), attrName.getLocalPart(), - attributes.getValue(i)); - } - } - } -} diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamHandler.java new file mode 100644 index 00000000..6323b446 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamHandler.java @@ -0,0 +1,139 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * 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 org.springframework.util.xml; + +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + +import org.springframework.util.Assert; + +/** + * SAX {@link org.xml.sax.ContentHandler} and {@link LexicalHandler} + * that writes to an {@link XMLStreamWriter}. + * + * @author Arjen Poutsma + * @since 4.0.3 + */ +class StaxStreamHandler extends AbstractStaxHandler { + + private final XMLStreamWriter streamWriter; + + + public StaxStreamHandler(XMLStreamWriter streamWriter) { + Assert.notNull(streamWriter, "XMLStreamWriter must not be null"); + this.streamWriter = streamWriter; + } + + + @Override + protected void startDocumentInternal() throws XMLStreamException { + this.streamWriter.writeStartDocument(); + } + + @Override + protected void endDocumentInternal() throws XMLStreamException { + this.streamWriter.writeEndDocument(); + } + + @Override + protected void startElementInternal(QName name, Attributes attributes, + Map<String, String> namespaceMapping) throws XMLStreamException { + + this.streamWriter.writeStartElement(name.getPrefix(), name.getLocalPart(), name.getNamespaceURI()); + + for (Map.Entry<String, String> entry : namespaceMapping.entrySet()) { + String prefix = entry.getKey(); + String namespaceUri = entry.getValue(); + this.streamWriter.writeNamespace(prefix, namespaceUri); + if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { + this.streamWriter.setDefaultNamespace(namespaceUri); + } + else { + this.streamWriter.setPrefix(prefix, namespaceUri); + } + } + for (int i = 0; i < attributes.getLength(); i++) { + QName attrName = toQName(attributes.getURI(i), attributes.getQName(i)); + if (!isNamespaceDeclaration(attrName)) { + this.streamWriter.writeAttribute(attrName.getPrefix(), attrName.getNamespaceURI(), + attrName.getLocalPart(), attributes.getValue(i)); + } + } + } + + @Override + protected void endElementInternal(QName name, Map<String, String> namespaceMapping) throws XMLStreamException { + this.streamWriter.writeEndElement(); + } + + @Override + protected void charactersInternal(String data) throws XMLStreamException { + this.streamWriter.writeCharacters(data); + } + + @Override + protected void cDataInternal(String data) throws XMLStreamException { + this.streamWriter.writeCData(data); + } + + @Override + protected void ignorableWhitespaceInternal(String data) throws XMLStreamException { + this.streamWriter.writeCharacters(data); + } + + @Override + protected void processingInstructionInternal(String target, String data) throws XMLStreamException { + this.streamWriter.writeProcessingInstruction(target, data); + } + + @Override + protected void dtdInternal(String dtd) throws XMLStreamException { + this.streamWriter.writeDTD(dtd); + } + + @Override + protected void commentInternal(String comment) throws XMLStreamException { + this.streamWriter.writeComment(comment); + } + + // Ignored + + @Override + public void setDocumentLocator(Locator locator) { + } + + @Override + public void startEntity(String name) throws SAXException { + } + + @Override + public void endEntity(String name) throws SAXException { + } + + @Override + protected void skippedEntityInternal(String name) throws XMLStreamException { + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java index 359514b5..ce186157 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java @@ -142,21 +142,27 @@ class StaxStreamXMLReader extends AbstractStaxXMLReader { if (getContentHandler() != null) { final Location location = this.reader.getLocation(); getContentHandler().setDocumentLocator(new Locator2() { + @Override public int getColumnNumber() { return (location != null ? location.getColumnNumber() : -1); } + @Override public int getLineNumber() { return (location != null ? location.getLineNumber() : -1); } + @Override public String getPublicId() { return (location != null ? location.getPublicId() : null); } + @Override public String getSystemId() { return (location != null ? location.getSystemId() : null); } + @Override public String getXMLVersion() { return xmlVersion; } + @Override public String getEncoding() { return encoding; } diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java index 41fd3f63..eb98be07 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java +++ b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,9 @@ import javax.xml.transform.stax.StAXSource; import org.xml.sax.ContentHandler; import org.xml.sax.XMLReader; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - /** - * Convenience methods for working with the StAX API. + * Convenience methods for working with the StAX API. Partly historic due to JAXP 1.3 compatibility; + * as of Spring 4.0, relying on JAXP 1.4 as included in JDK 1.6 and higher. * * <p>In particular, methods for using StAX ({@code javax.xml.stream}) in combination with the TrAX API * ({@code javax.xml.transform}), and converting StAX readers/writers into SAX readers/handlers and vice-versa. @@ -45,37 +43,31 @@ import org.springframework.util.ClassUtils; */ public abstract class StaxUtils { - // JAXP 1.4 is only available on JDK 1.6+ - private static boolean jaxp14Available = - ClassUtils.isPresent("javax.xml.transform.stax.StAXSource", StaxUtils.class.getClassLoader()); - - - // Stax Source - /** - * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLStreamReader}. + * Create a JAXP 1.4 {@link StAXSource} for the given {@link XMLStreamReader}. * @param streamReader the StAX stream reader * @return a source wrapping the {@code streamReader} */ - public static Source createCustomStaxSource(XMLStreamReader streamReader) { - return new StaxSource(streamReader); + public static Source createStaxSource(XMLStreamReader streamReader) { + return new StAXSource(streamReader); + } + + /** + * Create a JAXP 1.4 a {@link StAXSource} for the given {@link XMLEventReader}. + * @param eventReader the StAX event reader + * @return a source wrapping the {@code eventReader} + */ + public static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException { + return new StAXSource(eventReader); } /** - * Create a StAX {@link Source} for the given {@link XMLStreamReader}. - * <p>If JAXP 1.4 is available, this method returns a {@link StAXSource}; - * otherwise it returns a custom StAX Source. + * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLStreamReader}. * @param streamReader the StAX stream reader * @return a source wrapping the {@code streamReader} - * @see #createCustomStaxSource(XMLStreamReader) */ - public static Source createStaxSource(XMLStreamReader streamReader) { - if (jaxp14Available) { - return Jaxp14StaxHandler.createStaxSource(streamReader); - } - else { - return createCustomStaxSource(streamReader); - } + public static Source createCustomStaxSource(XMLStreamReader streamReader) { + return new StaxSource(streamReader); } /** @@ -88,43 +80,70 @@ public abstract class StaxUtils { } /** - * Create a StAX {@link Source} for the given {@link XMLEventReader}. - * <p>If JAXP 1.4 is available, this method returns a {@link StAXSource}; - * otherwise it returns a custom StAX Source. - * @param eventReader the StAX event reader - * @return a source wrapping the {@code eventReader} - * @throws XMLStreamException in case of StAX errors - * @see #createCustomStaxSource(XMLEventReader) + * Indicate whether the given {@link Source} is a JAXP 1.4 StAX Source or + * custom StAX Source. + * @return {@code true} if {@code source} is a JAXP 1.4 {@link StAXSource} or + * custom StAX Source; {@code false} otherwise */ - public static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException { - if (jaxp14Available) { - return Jaxp14StaxHandler.createStaxSource(eventReader); + public static boolean isStaxSource(Source source) { + return (source instanceof StAXSource || source instanceof StaxSource); + } + + /** + * Return the {@link XMLStreamReader} for the given StAX Source. + * @param source a JAXP 1.4 {@link StAXSource} + * @return the {@link XMLStreamReader} + * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXSource} + * or custom StAX Source + */ + public static XMLStreamReader getXMLStreamReader(Source source) { + if (source instanceof StAXSource) { + return ((StAXSource) source).getXMLStreamReader(); + } + else if (source instanceof StaxSource) { + return ((StaxSource) source).getXMLStreamReader(); } else { - return createCustomStaxSource(eventReader); + throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource"); } } /** - * Indicate whether the given {@link Source} is a StAX Source. - * @return {@code true} if {@code source} is a custom StAX source or JAXP - * 1.4 {@link StAXSource}; {@code false} otherwise. + * Return the {@link XMLEventReader} for the given StAX Source. + * @param source a JAXP 1.4 {@link StAXSource} + * @return the {@link XMLEventReader} + * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXSource} + * or custom StAX Source */ - public static boolean isStaxSource(Source source) { - return ((source instanceof StaxSource) || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source))); + public static XMLEventReader getXMLEventReader(Source source) { + if (source instanceof StAXSource) { + return ((StAXSource) source).getXMLEventReader(); + } + else if (source instanceof StaxSource) { + return ((StaxSource) source).getXMLEventReader(); + } + else { + throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource"); + } } /** - * Indicate whether the given class is a StAX Source class. - * @return {@code true} if {@code source} is a custom StAX source or JAXP - * 1.4 {@link StAXSource} class; {@code false} otherwise. + * Create a JAXP 1.4 {@link StAXResult} for the given {@link XMLStreamWriter}. + * @param streamWriter the StAX stream writer + * @return a result wrapping the {@code streamWriter} */ - public static boolean isStaxSourceClass(Class<? extends Source> clazz) { - return (StaxSource.class.equals(clazz) || (jaxp14Available && Jaxp14StaxHandler.isStaxSourceClass(clazz))); + public static Result createStaxResult(XMLStreamWriter streamWriter) { + return new StAXResult(streamWriter); } - - // Stax Result + /** + * Create a JAXP 1.4 {@link StAXResult} for the given {@link XMLEventWriter}. + * @param eventWriter the StAX event writer + * @return a result wrapping {@code streamReader} + */ + public static Result createStaxResult(XMLEventWriter eventWriter) { + return new StAXResult(eventWriter); + } /** * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLStreamWriter}. @@ -136,23 +155,6 @@ public abstract class StaxUtils { } /** - * Create a StAX {@link Result} for the given {@link XMLStreamWriter}. - * <p>If JAXP 1.4 is available, this method returns a {@link StAXResult}; - * otherwise it returns a custom StAX Result. - * @param streamWriter the StAX stream writer - * @return a result wrapping the {@code streamWriter} - * @see #createCustomStaxResult(XMLStreamWriter) - */ - public static Result createStaxResult(XMLStreamWriter streamWriter) { - if (jaxp14Available) { - return Jaxp14StaxHandler.createStaxResult(streamWriter); - } - else { - return createCustomStaxResult(streamWriter); - } - } - - /** * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLEventWriter}. * @param eventWriter the StAX event writer * @return a source wrapping the {@code eventWriter} @@ -162,86 +164,28 @@ public abstract class StaxUtils { } /** - * Create a StAX {@link Result} for the given {@link XMLEventWriter}. - * <p>If JAXP 1.4 is available, this method returns a {@link StAXResult}; otherwise it returns a + * Indicate whether the given {@link Result} is a JAXP 1.4 StAX Result or * custom StAX Result. - * @param eventWriter the StAX event writer - * @return a result wrapping {@code streamReader} - * @throws XMLStreamException in case of StAX errors - * @see #createCustomStaxResult(XMLEventWriter) - */ - public static Result createStaxResult(XMLEventWriter eventWriter) throws XMLStreamException { - if (jaxp14Available) { - return Jaxp14StaxHandler.createStaxResult(eventWriter); - } - else { - return createCustomStaxResult(eventWriter); - } - } - - /** - * Indicate whether the given {@link javax.xml.transform.Result} is a StAX Result. - * @return {@code true} if {@code result} is a custom Stax Result or JAXP 1.4 - * {@link StAXResult}; {@code false} otherwise. + * @return {@code true} if {@code result} is a JAXP 1.4 {@link StAXResult} or + * custom StAX Result; {@code false} otherwise */ public static boolean isStaxResult(Result result) { - return (result instanceof StaxResult || (jaxp14Available && Jaxp14StaxHandler.isStaxResult(result))); - } - - /** - * Return the {@link XMLStreamReader} for the given StAX Source. - * @param source a {@linkplain #createCustomStaxSource(XMLStreamReader) custom StAX Source} or - * JAXP 1.4 {@link StAXSource} - * @return the {@link XMLStreamReader} - * @throws IllegalArgumentException if {@code source} is neither a custom StAX Source - * nor JAXP 1.4 {@link StAXSource} - */ - public static XMLStreamReader getXMLStreamReader(Source source) { - if (source instanceof StaxSource) { - return ((StaxSource) source).getXMLStreamReader(); - } - else if (jaxp14Available) { - return Jaxp14StaxHandler.getXMLStreamReader(source); - } - else { - throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource"); - } - } - - /** - * Return the {@link XMLEventReader} for the given StAX Source. - * @param source a {@linkplain #createCustomStaxSource(XMLEventReader) custom StAX Source} or - * JAXP 1.4 {@link StAXSource} - * @return the {@link XMLEventReader} - * @throws IllegalArgumentException if {@code source} is neither a custom StAX Source - * nor a JAXP 1.4 {@link StAXSource} - */ - public static XMLEventReader getXMLEventReader(Source source) { - if (source instanceof StaxSource) { - return ((StaxSource) source).getXMLEventReader(); - } - else if (jaxp14Available) { - return Jaxp14StaxHandler.getXMLEventReader(source); - } - else { - throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource"); - } + return (result instanceof StAXResult || result instanceof StaxResult); } /** * Return the {@link XMLStreamWriter} for the given StAX Result. - * @param result a {@linkplain #createCustomStaxResult(XMLStreamWriter) custom StAX Result} or - * JAXP 1.4 {@link StAXResult} + * @param result a JAXP 1.4 {@link StAXResult} * @return the {@link XMLStreamReader} - * @throws IllegalArgumentException if {@code source} is neither a custom StAX Result - * nor a JAXP 1.4 {@link StAXResult} + * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXResult} + * or custom StAX Result */ public static XMLStreamWriter getXMLStreamWriter(Result result) { - if (result instanceof StaxResult) { - return ((StaxResult) result).getXMLStreamWriter(); + if (result instanceof StAXResult) { + return ((StAXResult) result).getXMLStreamWriter(); } - else if (jaxp14Available) { - return Jaxp14StaxHandler.getXMLStreamWriter(result); + else if (result instanceof StaxResult) { + return ((StaxResult) result).getXMLStreamWriter(); } else { throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult"); @@ -250,18 +194,17 @@ public abstract class StaxUtils { /** * Return the {@link XMLEventWriter} for the given StAX Result. - * @param result a {@linkplain #createCustomStaxResult(XMLEventWriter) custom StAX Result} or - * JAXP 1.4 {@link StAXResult} + * @param result a JAXP 1.4 {@link StAXResult} * @return the {@link XMLStreamReader} - * @throws IllegalArgumentException if {@code source} is neither a custom StAX Result - * nor a JAXP 1.4 {@link StAXResult} + * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXResult} + * or custom StAX Result */ public static XMLEventWriter getXMLEventWriter(Result result) { - if (result instanceof StaxResult) { - return ((StaxResult) result).getXMLEventWriter(); + if (result instanceof StAXResult) { + return ((StAXResult) result).getXMLEventWriter(); } - else if (jaxp14Available) { - return Jaxp14StaxHandler.getXMLEventWriter(result); + else if (result instanceof StaxResult) { + return ((StaxResult) result).getXMLEventWriter(); } else { throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult"); @@ -274,7 +217,7 @@ public abstract class StaxUtils { * @return a content handler writing to the {@code streamWriter} */ public static ContentHandler createContentHandler(XMLStreamWriter streamWriter) { - return new StaxStreamContentHandler(streamWriter); + return new StaxStreamHandler(streamWriter); } /** @@ -283,7 +226,7 @@ public abstract class StaxUtils { * @return a content handler writing to the {@code eventWriter} */ public static ContentHandler createContentHandler(XMLEventWriter eventWriter) { - return new StaxEventContentHandler(eventWriter); + return new StaxEventHandler(eventWriter); } /** @@ -305,8 +248,9 @@ public abstract class StaxUtils { } /** - * Return a {@link XMLStreamReader} that reads from a {@link XMLEventReader}. Useful, because the StAX - * {@code XMLInputFactory} allows one to create a event reader from a stream reader, but not vice-versa. + * Return a {@link XMLStreamReader} that reads from a {@link XMLEventReader}. + * Useful because the StAX {@code XMLInputFactory} allows one to create an + * event reader from a stream reader, but not vice-versa. * @return a stream reader that reads from an event reader */ public static XMLStreamReader createEventStreamReader(XMLEventReader eventReader) throws XMLStreamException { @@ -331,59 +275,4 @@ public abstract class StaxUtils { return new XMLEventStreamWriter(eventWriter, eventFactory); } - - /** - * Inner class to avoid a static JAXP 1.4 dependency. - */ - private static class Jaxp14StaxHandler { - - private static Source createStaxSource(XMLStreamReader streamReader) { - return new StAXSource(streamReader); - } - - private static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException { - return new StAXSource(eventReader); - } - - private static Result createStaxResult(XMLStreamWriter streamWriter) { - return new StAXResult(streamWriter); - } - - private static Result createStaxResult(XMLEventWriter eventWriter) { - return new StAXResult(eventWriter); - } - - private static boolean isStaxSource(Source source) { - return (source instanceof StAXSource); - } - - private static boolean isStaxSourceClass(Class<? extends Source> clazz) { - return StAXSource.class.equals(clazz); - } - - private static boolean isStaxResult(Result result) { - return (result instanceof StAXResult); - } - - private static XMLStreamReader getXMLStreamReader(Source source) { - Assert.isInstanceOf(StAXSource.class, source); - return ((StAXSource) source).getXMLStreamReader(); - } - - private static XMLEventReader getXMLEventReader(Source source) { - Assert.isInstanceOf(StAXSource.class, source); - return ((StAXSource) source).getXMLEventReader(); - } - - private static XMLStreamWriter getXMLStreamWriter(Result result) { - Assert.isInstanceOf(StAXResult.class, result); - return ((StAXResult) result).getXMLStreamWriter(); - } - - private static XMLEventWriter getXMLEventWriter(Result result) { - Assert.isInstanceOf(StAXResult.class, result); - return ((StAXResult) result).getXMLEventWriter(); - } - } - } diff --git a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java index b8321a05..134cbdc6 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java @@ -51,6 +51,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } + @Override public QName getName() { if (this.event.isStartElement()) { return this.event.asStartElement().getName(); @@ -63,14 +64,17 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override public Location getLocation() { return this.event.getLocation(); } + @Override public int getEventType() { return this.event.getEventType(); } + @Override public String getVersion() { if (this.event.isStartDocument()) { return ((StartDocument) this.event).getVersion(); @@ -80,10 +84,12 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override public Object getProperty(String name) throws IllegalArgumentException { return this.eventReader.getProperty(name); } + @Override public boolean isStandalone() { if (this.event.isStartDocument()) { return ((StartDocument) event).isStandalone(); @@ -93,6 +99,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override public boolean standaloneSet() { if (this.event.isStartDocument()) { return ((StartDocument) this.event).standaloneSet(); @@ -102,14 +109,17 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override public String getEncoding() { return null; } + @Override public String getCharacterEncodingScheme() { return null; } + @Override public String getPITarget() { if (this.event.isProcessingInstruction()) { return ((ProcessingInstruction) this.event).getTarget(); @@ -119,6 +129,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override public String getPIData() { if (this.event.isProcessingInstruction()) { return ((ProcessingInstruction) this.event).getData(); @@ -128,10 +139,12 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override public int getTextStart() { return 0; } + @Override public String getText() { if (this.event.isCharacters()) { return event.asCharacters().getData(); @@ -144,6 +157,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override @SuppressWarnings("rawtypes") public int getAttributeCount() { if (!this.event.isStartElement()) { @@ -153,18 +167,22 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { return countIterator(attributes); } + @Override public boolean isAttributeSpecified(int index) { return getAttribute(index).isSpecified(); } + @Override public QName getAttributeName(int index) { return getAttribute(index).getName(); } + @Override public String getAttributeType(int index) { return getAttribute(index).getDTDType(); } + @Override public String getAttributeValue(int index) { return getAttribute(index).getValue(); } @@ -188,6 +206,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { throw new IllegalArgumentException(); } + @Override public NamespaceContext getNamespaceContext() { if (this.event.isStartElement()) { return this.event.asStartElement().getNamespaceContext(); @@ -197,6 +216,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { } } + @Override @SuppressWarnings("rawtypes") public int getNamespaceCount() { Iterator namespaces; @@ -212,10 +232,12 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { return countIterator(namespaces); } + @Override public String getNamespacePrefix(int index) { return getNamespace(index).getPrefix(); } + @Override public String getNamespaceURI(int index) { return getNamespace(index).getNamespaceURI(); } @@ -245,11 +267,13 @@ class XMLEventStreamReader extends AbstractXMLStreamReader { throw new IllegalArgumentException(); } + @Override public int next() throws XMLStreamException { this.event = this.eventReader.nextEvent(); return this.event.getEventType(); } + @Override public void close() throws XMLStreamException { this.eventReader.close(); } diff --git a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java index d65c4a36..f910377d 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java @@ -60,56 +60,68 @@ class XMLEventStreamWriter implements XMLStreamWriter { } + @Override public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { this.eventWriter.setNamespaceContext(context); } + @Override public NamespaceContext getNamespaceContext() { return this.eventWriter.getNamespaceContext(); } + @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { this.eventWriter.setPrefix(prefix, uri); } + @Override public String getPrefix(String uri) throws XMLStreamException { return this.eventWriter.getPrefix(uri); } + @Override public void setDefaultNamespace(String uri) throws XMLStreamException { this.eventWriter.setDefaultNamespace(uri); } + @Override public Object getProperty(String name) throws IllegalArgumentException { throw new IllegalArgumentException(); } + @Override public void writeStartDocument() throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createStartDocument()); } + @Override public void writeStartDocument(String version) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createStartDocument(DEFAULT_ENCODING, version)); } + @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createStartDocument(encoding, version)); } + @Override public void writeStartElement(String localName) throws XMLStreamException { closeEmptyElementIfNecessary(); doWriteStartElement(this.eventFactory.createStartElement(new QName(localName), null, null)); } + @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { closeEmptyElementIfNecessary(); doWriteStartElement(this.eventFactory.createStartElement(new QName(namespaceURI, localName), null, null)); } + @Override public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { closeEmptyElementIfNecessary(); doWriteStartElement(this.eventFactory.createStartElement(new QName(namespaceURI, localName, prefix), null, null)); @@ -120,18 +132,21 @@ class XMLEventStreamWriter implements XMLStreamWriter { this.endElements.add(this.eventFactory.createEndElement(startElement.getName(), startElement.getNamespaces())); } + @Override public void writeEmptyElement(String localName) throws XMLStreamException { closeEmptyElementIfNecessary(); writeStartElement(localName); this.emptyElement = true; } + @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { closeEmptyElementIfNecessary(); writeStartElement(namespaceURI, localName); this.emptyElement = true; } + @Override public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { closeEmptyElementIfNecessary(); writeStartElement(prefix, localName, namespaceURI); @@ -145,6 +160,7 @@ class XMLEventStreamWriter implements XMLStreamWriter { } } + @Override public void writeEndElement() throws XMLStreamException { closeEmptyElementIfNecessary(); int last = this.endElements.size() - 1; @@ -153,24 +169,29 @@ class XMLEventStreamWriter implements XMLStreamWriter { this.endElements.remove(last); } + @Override public void writeAttribute(String localName, String value) throws XMLStreamException { this.eventWriter.add(this.eventFactory.createAttribute(localName, value)); } + @Override public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { this.eventWriter.add(this.eventFactory.createAttribute(new QName(namespaceURI, localName), value)); } + @Override public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException { this.eventWriter.add(this.eventFactory.createAttribute(prefix, namespaceURI, localName, value)); } + @Override public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { doWriteNamespace(this.eventFactory.createNamespace(prefix, namespaceURI)); } + @Override public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { doWriteNamespace(this.eventFactory.createNamespace(namespaceURI)); } @@ -191,55 +212,66 @@ class XMLEventStreamWriter implements XMLStreamWriter { this.endElements.set(last, newEndElement); } + @Override public void writeCharacters(String text) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createCharacters(text)); } + @Override public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createCharacters(new String(text, start, len))); } + @Override public void writeCData(String data) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createCData(data)); } + @Override public void writeComment(String data) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createComment(data)); } + @Override public void writeProcessingInstruction(String target) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, "")); } + @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, data)); } + @Override public void writeDTD(String dtd) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createDTD(dtd)); } + @Override public void writeEntityRef(String name) throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createEntityReference(name, null)); } + @Override public void writeEndDocument() throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.add(this.eventFactory.createEndDocument()); } + @Override public void flush() throws XMLStreamException { this.eventWriter.flush(); } + @Override public void close() throws XMLStreamException { closeEmptyElementIfNecessary(); this.eventWriter.close(); |