| /* | |
| * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.reflect.annotation; | |
| import java.lang.annotation.*; | |
| import java.lang.reflect.*; | |
| import java.nio.BufferUnderflowException; | |
| import java.nio.ByteBuffer; | |
| import java.util.*; | |
| import java.util.function.Supplier; | |
| import jdk.internal.reflect.ConstantPool; | |
| import sun.reflect.generics.parser.SignatureParser; | |
| import sun.reflect.generics.tree.TypeSignature; | |
| import sun.reflect.generics.factory.GenericsFactory; | |
| import sun.reflect.generics.factory.CoreReflectionFactory; | |
| import sun.reflect.generics.visitor.Reifier; | |
| import sun.reflect.generics.scope.ClassScope; | |
| /** | |
| * Parser for Java programming language annotations. Translates | |
| * annotation byte streams emitted by compiler into annotation objects. | |
| * | |
| * @author Josh Bloch | |
| * @since 1.5 | |
| */ | |
| public class AnnotationParser { | |
| /** | |
| * Parses the annotations described by the specified byte array. | |
| * resolving constant references in the specified constant pool. | |
| * The array must contain an array of annotations as described | |
| * in the RuntimeVisibleAnnotations_attribute: | |
| * | |
| * u2 num_annotations; | |
| * annotation annotations[num_annotations]; | |
| * | |
| * @throws AnnotationFormatError if an annotation is found to be | |
| * malformed. | |
| */ | |
| public static Map<Class<? extends Annotation>, Annotation> parseAnnotations( | |
| byte[] rawAnnotations, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| if (rawAnnotations == null) | |
| return Collections.emptyMap(); | |
| try { | |
| return parseAnnotations2(rawAnnotations, constPool, container, null); | |
| } catch(BufferUnderflowException e) { | |
| throw new AnnotationFormatError("Unexpected end of annotations."); | |
| } catch(IllegalArgumentException e) { | |
| // Type mismatch in constant pool | |
| throw new AnnotationFormatError(e); | |
| } | |
| } | |
| /** | |
| * Like {@link #parseAnnotations(byte[], sun.reflect.ConstantPool, Class)} | |
| * with an additional parameter {@code selectAnnotationClasses} which selects the | |
| * annotation types to parse (other than selected are quickly skipped).<p> | |
| * This method is used to parse select meta annotations in the construction | |
| * phase of {@link AnnotationType} instances to prevent infinite recursion. | |
| * | |
| * @param selectAnnotationClasses an array of annotation types to select when parsing | |
| */ | |
| @SafeVarargs | |
| @SuppressWarnings("varargs") // selectAnnotationClasses is used safely | |
| public static Map<Class<? extends Annotation>, Annotation> parseSelectAnnotations( | |
| byte[] rawAnnotations, | |
| ConstantPool constPool, | |
| Class<?> container, | |
| Class<? extends Annotation> ... selectAnnotationClasses) { | |
| if (rawAnnotations == null) | |
| return Collections.emptyMap(); | |
| try { | |
| return parseAnnotations2(rawAnnotations, constPool, container, selectAnnotationClasses); | |
| } catch(BufferUnderflowException e) { | |
| throw new AnnotationFormatError("Unexpected end of annotations."); | |
| } catch(IllegalArgumentException e) { | |
| // Type mismatch in constant pool | |
| throw new AnnotationFormatError(e); | |
| } | |
| } | |
| private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2( | |
| byte[] rawAnnotations, | |
| ConstantPool constPool, | |
| Class<?> container, | |
| Class<? extends Annotation>[] selectAnnotationClasses) { | |
| Map<Class<? extends Annotation>, Annotation> result = | |
| new LinkedHashMap<Class<? extends Annotation>, Annotation>(); | |
| ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); | |
| int numAnnotations = buf.getShort() & 0xFFFF; | |
| for (int i = 0; i < numAnnotations; i++) { | |
| Annotation a = parseAnnotation2(buf, constPool, container, false, selectAnnotationClasses); | |
| if (a != null) { | |
| Class<? extends Annotation> klass = a.annotationType(); | |
| if (AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME && | |
| result.put(klass, a) != null) { | |
| throw new AnnotationFormatError( | |
| "Duplicate annotation " + klass + " in " + container); | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Parses the parameter annotations described by the specified byte array. | |
| * resolving constant references in the specified constant pool. | |
| * The array must contain an array of annotations as described | |
| * in the RuntimeVisibleParameterAnnotations_attribute: | |
| * | |
| * u1 num_parameters; | |
| * { | |
| * u2 num_annotations; | |
| * annotation annotations[num_annotations]; | |
| * } parameter_annotations[num_parameters]; | |
| * | |
| * Unlike parseAnnotations, rawAnnotations must not be null! | |
| * A null value must be handled by the caller. This is so because | |
| * we cannot determine the number of parameters if rawAnnotations | |
| * is null. Also, the caller should check that the number | |
| * of parameters indicated by the return value of this method | |
| * matches the actual number of method parameters. A mismatch | |
| * indicates that an AnnotationFormatError should be thrown. | |
| * | |
| * @throws AnnotationFormatError if an annotation is found to be | |
| * malformed. | |
| */ | |
| public static Annotation[][] parseParameterAnnotations( | |
| byte[] rawAnnotations, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| try { | |
| return parseParameterAnnotations2(rawAnnotations, constPool, container); | |
| } catch(BufferUnderflowException e) { | |
| throw new AnnotationFormatError( | |
| "Unexpected end of parameter annotations."); | |
| } catch(IllegalArgumentException e) { | |
| // Type mismatch in constant pool | |
| throw new AnnotationFormatError(e); | |
| } | |
| } | |
| private static Annotation[][] parseParameterAnnotations2( | |
| byte[] rawAnnotations, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); | |
| int numParameters = buf.get() & 0xFF; | |
| Annotation[][] result = new Annotation[numParameters][]; | |
| for (int i = 0; i < numParameters; i++) { | |
| int numAnnotations = buf.getShort() & 0xFFFF; | |
| List<Annotation> annotations = | |
| new ArrayList<Annotation>(numAnnotations); | |
| for (int j = 0; j < numAnnotations; j++) { | |
| Annotation a = parseAnnotation(buf, constPool, container, false); | |
| if (a != null) { | |
| AnnotationType type = AnnotationType.getInstance( | |
| a.annotationType()); | |
| if (type.retention() == RetentionPolicy.RUNTIME) | |
| annotations.add(a); | |
| } | |
| } | |
| result[i] = annotations.toArray(EMPTY_ANNOTATIONS_ARRAY); | |
| } | |
| return result; | |
| } | |
| private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = | |
| new Annotation[0]; | |
| /** | |
| * Parses the annotation at the current position in the specified | |
| * byte buffer, resolving constant references in the specified constant | |
| * pool. The cursor of the byte buffer must point to an "annotation | |
| * structure" as described in the RuntimeVisibleAnnotations_attribute: | |
| * | |
| * annotation { | |
| * u2 type_index; | |
| * u2 num_member_value_pairs; | |
| * { u2 member_name_index; | |
| * member_value value; | |
| * } member_value_pairs[num_member_value_pairs]; | |
| * } | |
| * } | |
| * | |
| * Returns the annotation, or null if the annotation's type cannot | |
| * be found by the VM, or is not a valid annotation type. | |
| * | |
| * @param exceptionOnMissingAnnotationClass if true, throw | |
| * TypeNotPresentException if a referenced annotation type is not | |
| * available at runtime | |
| */ | |
| static Annotation parseAnnotation(ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container, | |
| boolean exceptionOnMissingAnnotationClass) { | |
| return parseAnnotation2(buf, constPool, container, exceptionOnMissingAnnotationClass, null); | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static Annotation parseAnnotation2(ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container, | |
| boolean exceptionOnMissingAnnotationClass, | |
| Class<? extends Annotation>[] selectAnnotationClasses) { | |
| int typeIndex = buf.getShort() & 0xFFFF; | |
| Class<? extends Annotation> annotationClass = null; | |
| String sig = "[unknown]"; | |
| try { | |
| sig = constPool.getUTF8At(typeIndex); | |
| annotationClass = (Class<? extends Annotation>)parseSig(sig, container); | |
| } catch (NoClassDefFoundError e) { | |
| if (exceptionOnMissingAnnotationClass) | |
| // note: at this point sig is "[unknown]" or VM-style | |
| // name instead of a binary name | |
| throw new TypeNotPresentException(sig, e); | |
| skipAnnotation(buf, false); | |
| return null; | |
| } | |
| catch (TypeNotPresentException e) { | |
| if (exceptionOnMissingAnnotationClass) | |
| throw e; | |
| skipAnnotation(buf, false); | |
| return null; | |
| } | |
| if (selectAnnotationClasses != null && !contains(selectAnnotationClasses, annotationClass)) { | |
| skipAnnotation(buf, false); | |
| return null; | |
| } | |
| AnnotationType type = null; | |
| try { | |
| type = AnnotationType.getInstance(annotationClass); | |
| } catch (IllegalArgumentException e) { | |
| skipAnnotation(buf, false); | |
| return null; | |
| } | |
| Map<String, Class<?>> memberTypes = type.memberTypes(); | |
| Map<String, Object> memberValues = | |
| new LinkedHashMap<String, Object>(type.memberDefaults()); | |
| int numMembers = buf.getShort() & 0xFFFF; | |
| for (int i = 0; i < numMembers; i++) { | |
| int memberNameIndex = buf.getShort() & 0xFFFF; | |
| String memberName = constPool.getUTF8At(memberNameIndex); | |
| Class<?> memberType = memberTypes.get(memberName); | |
| if (memberType == null) { | |
| // Member is no longer present in annotation type; ignore it | |
| skipMemberValue(buf); | |
| } else { | |
| Object value = parseMemberValue(memberType, buf, constPool, container); | |
| if (value instanceof AnnotationTypeMismatchExceptionProxy exceptProxy) | |
| exceptProxy.setMember(type.members().get(memberName)); | |
| memberValues.put(memberName, value); | |
| } | |
| } | |
| return annotationForMap(annotationClass, memberValues); | |
| } | |
| /** | |
| * Returns an annotation of the given type backed by the given | |
| * member {@literal ->} value map. | |
| */ | |
| public static Annotation annotationForMap(final Class<? extends Annotation> type, | |
| final Map<String, Object> memberValues) | |
| { | |
| return (Annotation) Proxy.newProxyInstance( | |
| type.getClassLoader(), new Class<?>[] { type }, | |
| new AnnotationInvocationHandler(type, memberValues)); | |
| } | |
| /** | |
| * Parses the annotation member value at the current position in the | |
| * specified byte buffer, resolving constant references in the specified | |
| * constant pool. The cursor of the byte buffer must point to a | |
| * "member_value structure" as described in the | |
| * RuntimeVisibleAnnotations_attribute: | |
| * | |
| * member_value { | |
| * u1 tag; | |
| * union { | |
| * u2 const_value_index; | |
| * { | |
| * u2 type_name_index; | |
| * u2 const_name_index; | |
| * } enum_const_value; | |
| * u2 class_info_index; | |
| * annotation annotation_value; | |
| * { | |
| * u2 num_values; | |
| * member_value values[num_values]; | |
| * } array_value; | |
| * } value; | |
| * } | |
| * | |
| * The member must be of the indicated type. If it is not, this | |
| * method returns an AnnotationTypeMismatchExceptionProxy. | |
| */ | |
| @SuppressWarnings("unchecked") | |
| public static Object parseMemberValue(Class<?> memberType, | |
| ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| // Note that VMSupport.encodeAnnotation (used by JVMCI) may need to | |
| // be updated if new annotation member types are added. | |
| Object result = null; | |
| int tag = buf.get(); | |
| switch(tag) { | |
| case 'e': | |
| return parseEnumValue((Class<? extends Enum<?>>)memberType, buf, constPool, container); | |
| case 'c': | |
| result = parseClassValue(buf, constPool, container); | |
| break; | |
| case '@': | |
| result = parseAnnotation(buf, constPool, container, true); | |
| break; | |
| case '[': | |
| return parseArray(memberType, buf, constPool, container); | |
| default: | |
| result = parseConst(tag, buf, constPool); | |
| } | |
| if (result == null) { | |
| result = new AnnotationTypeMismatchExceptionProxy( | |
| Proxy.isProxyClass(memberType) | |
| ? memberType.getInterfaces()[0].getName() | |
| : memberType.getName()); | |
| } else if (!(result instanceof ExceptionProxy) && | |
| !memberType.isInstance(result)) { | |
| if (result instanceof Annotation) { | |
| result = new AnnotationTypeMismatchExceptionProxy( | |
| result.toString()); | |
| } else { | |
| result = new AnnotationTypeMismatchExceptionProxy( | |
| result.getClass().getName() + "[" + result + "]"); | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Parses the primitive or String annotation member value indicated by | |
| * the specified tag byte at the current position in the specified byte | |
| * buffer, resolving constant reference in the specified constant pool. | |
| * The cursor of the byte buffer must point to an annotation member value | |
| * of the type indicated by the specified tag, as described in the | |
| * RuntimeVisibleAnnotations_attribute: | |
| * | |
| * u2 const_value_index; | |
| */ | |
| private static Object parseConst(int tag, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| int constIndex = buf.getShort() & 0xFFFF; | |
| switch(tag) { | |
| case 'B': | |
| return Byte.valueOf((byte) constPool.getIntAt(constIndex)); | |
| case 'C': | |
| return Character.valueOf((char) constPool.getIntAt(constIndex)); | |
| case 'D': | |
| return Double.valueOf(constPool.getDoubleAt(constIndex)); | |
| case 'F': | |
| return Float.valueOf(constPool.getFloatAt(constIndex)); | |
| case 'I': | |
| return Integer.valueOf(constPool.getIntAt(constIndex)); | |
| case 'J': | |
| return Long.valueOf(constPool.getLongAt(constIndex)); | |
| case 'S': | |
| return Short.valueOf((short) constPool.getIntAt(constIndex)); | |
| case 'Z': | |
| return Boolean.valueOf(constPool.getIntAt(constIndex) != 0); | |
| case 's': | |
| return constPool.getUTF8At(constIndex); | |
| default: | |
| throw new AnnotationFormatError( | |
| "Invalid member-value tag in annotation: " + tag); | |
| } | |
| } | |
| /** | |
| * Parses the Class member value at the current position in the | |
| * specified byte buffer, resolving constant references in the specified | |
| * constant pool. The cursor of the byte buffer must point to a "class | |
| * info index" as described in the RuntimeVisibleAnnotations_attribute: | |
| * | |
| * u2 class_info_index; | |
| */ | |
| private static Object parseClassValue(ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| int classIndex = buf.getShort() & 0xFFFF; | |
| try { | |
| String sig = constPool.getUTF8At(classIndex); | |
| return parseSig(sig, container); | |
| } catch (NoClassDefFoundError e) { | |
| return new TypeNotPresentExceptionProxy("[unknown]", e); | |
| } catch (TypeNotPresentException e) { | |
| return new TypeNotPresentExceptionProxy(e.typeName(), e.getCause()); | |
| } | |
| } | |
| private static Class<?> parseSig(String sig, Class<?> container) { | |
| if (sig.equals("V")) return void.class; | |
| SignatureParser parser = SignatureParser.make(); | |
| TypeSignature typeSig = parser.parseTypeSig(sig); | |
| GenericsFactory factory = CoreReflectionFactory.make(container, ClassScope.make(container)); | |
| Reifier reify = Reifier.make(factory); | |
| typeSig.accept(reify); | |
| Type result = reify.getResult(); | |
| return toClass(result); | |
| } | |
| static Class<?> toClass(Type o) { | |
| if (o instanceof GenericArrayType gat) | |
| return toClass(gat.getGenericComponentType()).arrayType(); | |
| return (Class<?>) o; | |
| } | |
| /** | |
| * Parses the enum constant member value at the current position in the | |
| * specified byte buffer, resolving constant references in the specified | |
| * constant pool. The cursor of the byte buffer must point to a | |
| * "enum_const_value structure" as described in the | |
| * RuntimeVisibleAnnotations_attribute: | |
| * | |
| * { | |
| * u2 type_name_index; | |
| * u2 const_name_index; | |
| * } enum_const_value; | |
| */ | |
| @SuppressWarnings({"rawtypes", "unchecked"}) | |
| private static Object parseEnumValue(Class<? extends Enum> enumType, ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| int typeNameIndex = buf.getShort() & 0xFFFF; | |
| String typeName = constPool.getUTF8At(typeNameIndex); | |
| int constNameIndex = buf.getShort() & 0xFFFF; | |
| String constName = constPool.getUTF8At(constNameIndex); | |
| if (!enumType.isEnum() || enumType != parseSig(typeName, container)) { | |
| return new AnnotationTypeMismatchExceptionProxy( | |
| typeName.substring(1, typeName.length() - 1).replace('/', '.') + "." + constName); | |
| } | |
| try { | |
| return Enum.valueOf(enumType, constName); | |
| } catch(IllegalArgumentException e) { | |
| return new EnumConstantNotPresentExceptionProxy( | |
| (Class<? extends Enum<?>>)enumType, constName); | |
| } | |
| } | |
| /** | |
| * Parses the array value at the current position in the specified byte | |
| * buffer, resolving constant references in the specified constant pool. | |
| * The cursor of the byte buffer must point to an array value struct | |
| * as specified in the RuntimeVisibleAnnotations_attribute: | |
| * | |
| * { | |
| * u2 num_values; | |
| * member_value values[num_values]; | |
| * } array_value; | |
| * | |
| * If the array values do not match arrayType, an | |
| * AnnotationTypeMismatchExceptionProxy will be returned. | |
| */ | |
| @SuppressWarnings("unchecked") | |
| private static Object parseArray(Class<?> arrayType, | |
| ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| int length = buf.getShort() & 0xFFFF; // Number of array components | |
| if (!arrayType.isArray()) { | |
| return parseUnknownArray(length, buf); | |
| } | |
| Class<?> componentType = arrayType.getComponentType(); | |
| if (componentType == byte.class) { | |
| return parseByteArray(length, buf, constPool); | |
| } else if (componentType == char.class) { | |
| return parseCharArray(length, buf, constPool); | |
| } else if (componentType == double.class) { | |
| return parseDoubleArray(length, buf, constPool); | |
| } else if (componentType == float.class) { | |
| return parseFloatArray(length, buf, constPool); | |
| } else if (componentType == int.class) { | |
| return parseIntArray(length, buf, constPool); | |
| } else if (componentType == long.class) { | |
| return parseLongArray(length, buf, constPool); | |
| } else if (componentType == short.class) { | |
| return parseShortArray(length, buf, constPool); | |
| } else if (componentType == boolean.class) { | |
| return parseBooleanArray(length, buf, constPool); | |
| } else if (componentType == String.class) { | |
| return parseStringArray(length, buf, constPool); | |
| } else if (componentType == Class.class) { | |
| return parseClassArray(length, buf, constPool, container); | |
| } else if (componentType.isEnum()) { | |
| return parseEnumArray(length, (Class<? extends Enum<?>>)componentType, buf, | |
| constPool, container); | |
| } else if (componentType.isAnnotation()) { | |
| return parseAnnotationArray(length, (Class <? extends Annotation>)componentType, buf, | |
| constPool, container); | |
| } else { | |
| return parseUnknownArray(length, buf); | |
| } | |
| } | |
| private static Object parseByteArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| byte[] result = new byte[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'B') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = (byte) constPool.getIntAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseCharArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| char[] result = new char[length]; | |
| boolean typeMismatch = false; | |
| byte tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'C') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = (char) constPool.getIntAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseDoubleArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| double[] result = new double[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'D') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = constPool.getDoubleAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseFloatArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| float[] result = new float[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'F') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = constPool.getFloatAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseIntArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| int[] result = new int[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'I') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = constPool.getIntAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseLongArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| long[] result = new long[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'J') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = constPool.getLongAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseShortArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| short[] result = new short[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'S') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = (short) constPool.getIntAt(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseBooleanArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| boolean[] result = new boolean[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 'Z') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = (constPool.getIntAt(index) != 0); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseStringArray(int length, | |
| ByteBuffer buf, ConstantPool constPool) { | |
| String[] result = new String[length]; | |
| boolean typeMismatch = false; | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| if (tag == 's') { | |
| int index = buf.getShort() & 0xFFFF; | |
| result[i] = constPool.getUTF8At(index); | |
| } else { | |
| skipMemberValue(tag, buf); | |
| typeMismatch = true; | |
| } | |
| } | |
| return typeMismatch ? exceptionProxy(tag) : result; | |
| } | |
| private static Object parseClassArray(int length, | |
| ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| return parseArrayElements(new Class<?>[length], | |
| buf, 'c', () -> parseClassValue(buf, constPool, container)); | |
| } | |
| private static Object parseEnumArray(int length, Class<? extends Enum<?>> enumType, | |
| ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| return parseArrayElements((Object[]) Array.newInstance(enumType, length), | |
| buf, 'e', () -> parseEnumValue(enumType, buf, constPool, container)); | |
| } | |
| private static Object parseAnnotationArray(int length, | |
| Class<? extends Annotation> annotationType, | |
| ByteBuffer buf, | |
| ConstantPool constPool, | |
| Class<?> container) { | |
| return parseArrayElements((Object[]) Array.newInstance(annotationType, length), | |
| buf, '@', () -> parseAnnotation(buf, constPool, container, true)); | |
| } | |
| private static Object parseArrayElements(Object[] result, | |
| ByteBuffer buf, | |
| int expectedTag, | |
| Supplier<Object> parseElement) { | |
| Object exceptionProxy = null; | |
| for (int i = 0; i < result.length; i++) { | |
| int tag = buf.get(); | |
| if (tag == expectedTag) { | |
| Object value = parseElement.get(); | |
| if (value instanceof ExceptionProxy proxyValue) { | |
| if (exceptionProxy == null) | |
| exceptionProxy = proxyValue; | |
| } else { | |
| result[i] = value; | |
| } | |
| } else { | |
| skipMemberValue(tag, buf); | |
| if (exceptionProxy == null) exceptionProxy = exceptionProxy(tag); | |
| } | |
| } | |
| return (exceptionProxy != null) ? exceptionProxy : result; | |
| } | |
| private static Object parseUnknownArray(int length, | |
| ByteBuffer buf) { | |
| int tag = 0; | |
| for (int i = 0; i < length; i++) { | |
| tag = buf.get(); | |
| skipMemberValue(tag, buf); | |
| } | |
| return exceptionProxy(tag); | |
| } | |
| /** | |
| * Returns an appropriate exception proxy for a mismatching array | |
| * annotation where the erroneous array has the specified tag. | |
| */ | |
| private static ExceptionProxy exceptionProxy(int tag) { | |
| return new AnnotationTypeMismatchExceptionProxy( | |
| "Array with component tag: " + (tag == 0 ? "0" : (char) tag)); | |
| } | |
| /** | |
| * Skips the annotation at the current position in the specified | |
| * byte buffer. The cursor of the byte buffer must point to | |
| * an "annotation structure" OR two bytes into an annotation | |
| * structure (i.e., after the type index). | |
| * | |
| * @param complete true if the byte buffer points to the beginning | |
| * of an annotation structure (rather than two bytes in). | |
| */ | |
| private static void skipAnnotation(ByteBuffer buf, boolean complete) { | |
| if (complete) | |
| buf.getShort(); // Skip type index | |
| int numMembers = buf.getShort() & 0xFFFF; | |
| for (int i = 0; i < numMembers; i++) { | |
| buf.getShort(); // Skip memberNameIndex | |
| skipMemberValue(buf); | |
| } | |
| } | |
| /** | |
| * Skips the annotation member value at the current position in the | |
| * specified byte buffer. The cursor of the byte buffer must point to a | |
| * "member_value structure." | |
| */ | |
| private static void skipMemberValue(ByteBuffer buf) { | |
| int tag = buf.get(); | |
| skipMemberValue(tag, buf); | |
| } | |
| /** | |
| * Skips the annotation member value at the current position in the | |
| * specified byte buffer. The cursor of the byte buffer must point | |
| * immediately after the tag in a "member_value structure." | |
| */ | |
| private static void skipMemberValue(int tag, ByteBuffer buf) { | |
| switch(tag) { | |
| case 'e': // Enum value | |
| buf.getInt(); // (Two shorts, actually.) | |
| break; | |
| case '@': | |
| skipAnnotation(buf, true); | |
| break; | |
| case '[': | |
| skipArray(buf); | |
| break; | |
| default: | |
| // Class, primitive, or String | |
| buf.getShort(); | |
| } | |
| } | |
| /** | |
| * Skips the array value at the current position in the specified byte | |
| * buffer. The cursor of the byte buffer must point to an array value | |
| * struct. | |
| */ | |
| private static void skipArray(ByteBuffer buf) { | |
| int length = buf.getShort() & 0xFFFF; | |
| for (int i = 0; i < length; i++) | |
| skipMemberValue(buf); | |
| } | |
| /** | |
| * Searches for given {@code element} in given {@code array} by identity. | |
| * Returns {@code true} if found {@code false} if not. | |
| */ | |
| private static boolean contains(Object[] array, Object element) { | |
| for (Object e : array) | |
| if (e == element) | |
| return true; | |
| return false; | |
| } | |
| /* | |
| * This method converts the annotation map returned by the parseAnnotations() | |
| * method to an array. It is called by Field.getDeclaredAnnotations(), | |
| * Method.getDeclaredAnnotations(), and Constructor.getDeclaredAnnotations(). | |
| * This avoids the reflection classes to load the Annotation class until | |
| * it is needed. | |
| */ | |
| private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; | |
| public static Annotation[] toArray(Map<Class<? extends Annotation>, Annotation> annotations) { | |
| return annotations.values().toArray(EMPTY_ANNOTATION_ARRAY); | |
| } | |
| static Annotation[] getEmptyAnnotationArray() { return EMPTY_ANNOTATION_ARRAY; } | |
| } |