/******************************************************************************* * Copyright (c) 2007, 2014 BEA Systems, Inc. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * wharley@bea.com - initial API and implementation * IBM Corporation - fix for 342598 * IBM Corporation - Java 8 support * het@google.com - Bug 427943 - The method org.eclipse.jdt.internal.compiler.apt.model.Factory.getPrimitiveType does not throw IllegalArgumentException *******************************************************************************/ package org.eclipse.jdt.internal.compiler.apt.model; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.ErrorType; import javax.lang.model.type.NoType; import javax.lang.model.type.NullType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair; import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.PackageBinding; import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; /** * Creates javax.lang.model wrappers around JDT internal compiler bindings. */ public class Factory { // using auto-boxing to take advantage of caching, if any. // the dummy value picked here falls within the caching range. public static final Byte DUMMY_BYTE = 0; public static final Character DUMMY_CHAR = '0'; public static final Double DUMMY_DOUBLE = 0d; public static final Float DUMMY_FLOAT = 0f; public static final Integer DUMMY_INTEGER = 0; public static final Long DUMMY_LONG = 0l; public static final Short DUMMY_SHORT = 0; private final BaseProcessingEnvImpl _env; public static List<? extends AnnotationMirror> EMPTY_ANNOTATION_MIRRORS = Collections.emptyList(); /** * This object should only be constructed by the BaseProcessingEnvImpl. */ public Factory(BaseProcessingEnvImpl env) { _env = env; } /** * Convert an array of compiler annotation bindings into a list of AnnotationMirror * @return a non-null, possibly empty, unmodifiable list. */ public List<? extends AnnotationMirror> getAnnotationMirrors(AnnotationBinding[] annotations) { if (null == annotations || 0 == annotations.length) { return Collections.emptyList(); } List<AnnotationMirror> list = new ArrayList<AnnotationMirror>(annotations.length); for (AnnotationBinding annotation : annotations) { if (annotation == null) continue; list.add(newAnnotationMirror(annotation)); } return Collections.unmodifiableList(list); } @SuppressWarnings("unchecked") // for the cast to A public <A extends Annotation> A[] getAnnotationsByType(AnnotationBinding[] annoInstances, Class<A> annotationClass) { A[] result = getAnnotations(annoInstances, annotationClass, false); return result == null ? (A[]) Array.newInstance(annotationClass, 0) : result; } public <A extends Annotation> A getAnnotation(AnnotationBinding[] annoInstances, Class<A> annotationClass) { A[] result = getAnnotations(annoInstances, annotationClass, true); return result == null ? null : result[0]; } @SuppressWarnings("unchecked") // for cast of newProxyInstance() to A private <A extends Annotation> A[] getAnnotations(AnnotationBinding[] annoInstances, Class<A> annotationClass, boolean justTheFirst) { if(annoInstances == null || annoInstances.length == 0 || annotationClass == null ) return null; String annoTypeName = annotationClass.getName(); if(annoTypeName == null ) return null; List<A> list = new ArrayList<A>(annoInstances.length); for(AnnotationBinding annoInstance : annoInstances) { if (annoInstance == null) continue; AnnotationMirrorImpl annoMirror = createAnnotationMirror(annoTypeName, annoInstance); if (annoMirror != null) { list.add((A)Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[]{ annotationClass }, annoMirror)); if (justTheFirst) break; } } A [] result = (A[]) Array.newInstance(annotationClass, list.size()); return list.size() > 0 ? (A[]) list.toArray(result) : null; } private AnnotationMirrorImpl createAnnotationMirror(String annoTypeName, AnnotationBinding annoInstance) { ReferenceBinding binding = annoInstance.getAnnotationType(); if (binding != null && binding.isAnnotationType() ) { char[] qName; if (binding.isMemberType()) { annoTypeName = annoTypeName.replace('$', '.'); qName = CharOperation.concatWith(binding.enclosingType().compoundName, binding.sourceName, '.'); CharOperation.replace(qName, '$', '.'); } else { qName = CharOperation.concatWith(binding.compoundName, '.'); } if(annoTypeName.equals(new String(qName)) ){ return (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror(annoInstance); } } return null; } private static void appendModifier(Set<Modifier> result, int modifiers, int modifierConstant, Modifier modifier) { if ((modifiers & modifierConstant) != 0) { result.add(modifier); } } private static void decodeModifiers(Set<Modifier> result, int modifiers, int[] checkBits) { if (checkBits == null) return; for (int i = 0, max = checkBits.length; i < max; i++) { switch(checkBits[i]) { case ClassFileConstants.AccPublic : appendModifier(result, modifiers, checkBits[i], Modifier.PUBLIC); break; case ClassFileConstants.AccProtected: appendModifier(result, modifiers, checkBits[i], Modifier.PROTECTED); break; case ClassFileConstants.AccPrivate : appendModifier(result, modifiers, checkBits[i], Modifier.PRIVATE); break; case ClassFileConstants.AccAbstract : appendModifier(result, modifiers, checkBits[i], Modifier.ABSTRACT); break; case ExtraCompilerModifiers.AccDefaultMethod : try { appendModifier(result, modifiers, checkBits[i], Modifier.valueOf("DEFAULT")); //$NON-NLS-1$ } catch(IllegalArgumentException iae) { // Don't have JDK 1.8, just ignore and proceed. } break; case ClassFileConstants.AccStatic : appendModifier(result, modifiers, checkBits[i], Modifier.STATIC); break; case ClassFileConstants.AccFinal : appendModifier(result, modifiers, checkBits[i], Modifier.FINAL); break; case ClassFileConstants.AccSynchronized : appendModifier(result, modifiers, checkBits[i], Modifier.SYNCHRONIZED); break; case ClassFileConstants.AccNative : appendModifier(result, modifiers, checkBits[i], Modifier.NATIVE); break; case ClassFileConstants.AccStrictfp : appendModifier(result, modifiers, checkBits[i], Modifier.STRICTFP); break; case ClassFileConstants.AccTransient : appendModifier(result, modifiers, checkBits[i], Modifier.TRANSIENT); break; case ClassFileConstants.AccVolatile : appendModifier(result, modifiers, checkBits[i], Modifier.VOLATILE); break; } } } public static Object getMatchingDummyValue(final Class<?> expectedType){ if( expectedType.isPrimitive() ){ if(expectedType == boolean.class) return Boolean.FALSE; else if( expectedType == byte.class ) return DUMMY_BYTE; else if( expectedType == char.class ) return DUMMY_CHAR; else if( expectedType == double.class) return DUMMY_DOUBLE; else if( expectedType == float.class ) return DUMMY_FLOAT; else if( expectedType == int.class ) return DUMMY_INTEGER; else if( expectedType == long.class ) return DUMMY_LONG; else if(expectedType == short.class) return DUMMY_SHORT; else // expectedType == void.class. can this happen? return DUMMY_INTEGER; // anything would work } else return null; } public TypeMirror getReceiverType(MethodBinding binding) { if (binding != null) { if (binding.receiver != null) { return _env.getFactory().newTypeMirror(binding.receiver); } if (binding.declaringClass != null) { if (!binding.isStatic() && (!binding.isConstructor() || binding.declaringClass.isMemberType())) { return _env.getFactory().newTypeMirror(binding.declaringClass); } } } return NoTypeImpl.NO_TYPE_NONE; } public static Set<Modifier> getModifiers(int modifiers, ElementKind kind) { return getModifiers(modifiers, kind, false); } /** * Convert from the JDT's ClassFileConstants flags to the Modifier enum. */ public static Set<Modifier> getModifiers(int modifiers, ElementKind kind, boolean isFromBinary) { EnumSet<Modifier> result = EnumSet.noneOf(Modifier.class); switch(kind) { case CONSTRUCTOR : case METHOD : // modifiers for methods decodeModifiers(result, modifiers, new int[] { ClassFileConstants.AccPublic, ClassFileConstants.AccProtected, ClassFileConstants.AccPrivate, ClassFileConstants.AccAbstract, ClassFileConstants.AccStatic, ClassFileConstants.AccFinal, ClassFileConstants.AccSynchronized, ClassFileConstants.AccNative, ClassFileConstants.AccStrictfp, ExtraCompilerModifiers.AccDefaultMethod }); break; case FIELD : case ENUM_CONSTANT : // for fields decodeModifiers(result, modifiers, new int[] { ClassFileConstants.AccPublic, ClassFileConstants.AccProtected, ClassFileConstants.AccPrivate, ClassFileConstants.AccStatic, ClassFileConstants.AccFinal, ClassFileConstants.AccTransient, ClassFileConstants.AccVolatile }); break; case ENUM : if (isFromBinary) { decodeModifiers(result, modifiers, new int[] { ClassFileConstants.AccPublic, ClassFileConstants.AccProtected, ClassFileConstants.AccFinal, ClassFileConstants.AccPrivate, ClassFileConstants.AccAbstract, ClassFileConstants.AccStatic, ClassFileConstants.AccStrictfp }); } else { // enum from source cannot be explicitly abstract decodeModifiers(result, modifiers, new int[] { ClassFileConstants.AccPublic, ClassFileConstants.AccProtected, ClassFileConstants.AccFinal, ClassFileConstants.AccPrivate, ClassFileConstants.AccStatic, ClassFileConstants.AccStrictfp }); } break; case ANNOTATION_TYPE : case INTERFACE : case CLASS : // for type decodeModifiers(result, modifiers, new int[] { ClassFileConstants.AccPublic, ClassFileConstants.AccProtected, ClassFileConstants.AccAbstract, ClassFileConstants.AccFinal, ClassFileConstants.AccPrivate, ClassFileConstants.AccStatic, ClassFileConstants.AccStrictfp }); break; default: break; } return Collections.unmodifiableSet(result); } public AnnotationMirror newAnnotationMirror(AnnotationBinding binding) { return new AnnotationMirrorImpl(_env, binding); } /** * Create a new element that knows what kind it is even if the binding is unresolved. */ public Element newElement(Binding binding, ElementKind kindHint) { if (binding == null) return null; switch (binding.kind()) { case Binding.FIELD: case Binding.LOCAL: case Binding.VARIABLE: return new VariableElementImpl(_env, (VariableBinding) binding); case Binding.TYPE: case Binding.GENERIC_TYPE: ReferenceBinding referenceBinding = (ReferenceBinding)binding; if ((referenceBinding.tagBits & TagBits.HasMissingType) != 0) { return new ErrorTypeElement(this._env, referenceBinding); } if (CharOperation.equals(referenceBinding.sourceName, TypeConstants.PACKAGE_INFO_NAME)) { return new PackageElementImpl(_env, referenceBinding.fPackage); } return new TypeElementImpl(_env, referenceBinding, kindHint); case Binding.METHOD: return new ExecutableElementImpl(_env, (MethodBinding)binding); case Binding.RAW_TYPE: case Binding.PARAMETERIZED_TYPE: return new TypeElementImpl(_env, ((ParameterizedTypeBinding)binding).genericType(), kindHint); case Binding.PACKAGE: return new PackageElementImpl(_env, (PackageBinding)binding); case Binding.TYPE_PARAMETER: return new TypeParameterElementImpl(_env, (TypeVariableBinding)binding); // TODO: fill in the rest of these case Binding.IMPORT: case Binding.ARRAY_TYPE: case Binding.BASE_TYPE: case Binding.WILDCARD_TYPE: case Binding.INTERSECTION_TYPE: throw new UnsupportedOperationException("NYI: binding type " + binding.kind()); //$NON-NLS-1$ } return null; } public Element newElement(Binding binding) { return newElement(binding, null); } /** * Convenience method - equivalent to {@code (PackageElement)Factory.newElement(binding)} */ public PackageElement newPackageElement(PackageBinding binding) { return new PackageElementImpl(_env, binding); } public NullType getNullType() { return NoTypeImpl.NULL_TYPE; } public NoType getNoType(TypeKind kind) { switch (kind) { case NONE: return NoTypeImpl.NO_TYPE_NONE; case VOID: return NoTypeImpl.NO_TYPE_VOID; case PACKAGE: return NoTypeImpl.NO_TYPE_PACKAGE; default: throw new IllegalArgumentException(); } } /** * Get a type mirror object representing the specified primitive type kind. * @throw IllegalArgumentException if a non-primitive TypeKind is requested */ public PrimitiveTypeImpl getPrimitiveType(TypeKind kind) { switch (kind) { case BOOLEAN: return PrimitiveTypeImpl.BOOLEAN; case BYTE: return PrimitiveTypeImpl.BYTE; case CHAR: return PrimitiveTypeImpl.CHAR; case DOUBLE: return PrimitiveTypeImpl.DOUBLE; case FLOAT: return PrimitiveTypeImpl.FLOAT; case INT: return PrimitiveTypeImpl.INT; case LONG: return PrimitiveTypeImpl.LONG; case SHORT: return PrimitiveTypeImpl.SHORT; default: throw new IllegalArgumentException(); } } public PrimitiveTypeImpl getPrimitiveType(BaseTypeBinding binding) { AnnotationBinding[] annotations = binding.getTypeAnnotations(); if (annotations == null || annotations.length == 0) { return getPrimitiveType(PrimitiveTypeImpl.getKind(binding)); } return new PrimitiveTypeImpl(_env, binding); } /** * Given a binding of uncertain type, try to create the right sort of TypeMirror for it. */ public TypeMirror newTypeMirror(Binding binding) { switch (binding.kind()) { case Binding.FIELD: case Binding.LOCAL: case Binding.VARIABLE: // For variables, return the type of the variable return newTypeMirror(((VariableBinding)binding).type); case Binding.PACKAGE: return getNoType(TypeKind.PACKAGE); case Binding.IMPORT: throw new UnsupportedOperationException("NYI: import type " + binding.kind()); //$NON-NLS-1$ case Binding.METHOD: return new ExecutableTypeImpl(_env, (MethodBinding) binding); case Binding.TYPE: case Binding.RAW_TYPE: case Binding.GENERIC_TYPE: case Binding.PARAMETERIZED_TYPE: ReferenceBinding referenceBinding = (ReferenceBinding) binding; if ((referenceBinding.tagBits & TagBits.HasMissingType) != 0) { return getErrorType(referenceBinding); } return new DeclaredTypeImpl(_env, (ReferenceBinding)binding); case Binding.ARRAY_TYPE: return new ArrayTypeImpl(_env, (ArrayBinding)binding); case Binding.BASE_TYPE: BaseTypeBinding btb = (BaseTypeBinding)binding; switch (btb.id) { case TypeIds.T_void: return getNoType(TypeKind.VOID); case TypeIds.T_null: return getNullType(); default: return getPrimitiveType(btb); } case Binding.WILDCARD_TYPE: case Binding.INTERSECTION_TYPE: // TODO compatible, but shouldn't it really be an intersection type? return new WildcardTypeImpl(_env, (WildcardBinding) binding); case Binding.TYPE_PARAMETER: return new TypeVariableImpl(_env, (TypeVariableBinding) binding); } return null; } /** * @param declaringElement the class, method, etc. that is parameterized by this parameter. */ public TypeParameterElement newTypeParameterElement(TypeVariableBinding variable, Element declaringElement) { return new TypeParameterElementImpl(_env, variable, declaringElement); } public ErrorType getErrorType(ReferenceBinding binding) { return new ErrorTypeImpl(this._env, binding); } /** * This method is derived from code in org.eclipse.jdt.apt.core. * * This method is designed to be invoked by the invocation handler and anywhere that requires * a AnnotationValue (AnnotationMirror member values and default values from annotation member). * * Regardless of the path, there are common primitive type conversion that needs to take place. * The type conversions respect the type widening and narrowing rules from JLS 5.1.2 and 5.1.2. * * The only question remains is what is the type of the return value when the type conversion fails? * When <code>avoidReflectException</code> is set to <code>true</code> * Return <code>false</code> if the expected type is <code>boolean</code> * Return numeric 0 for all numeric primitive types and '0' for <code>char</code> * * Otherwise: * Return the value unchanged. * * In the invocation handler case: * The value returned by {@link java.lang.reflect.InvocationHandler#invoke} * will be converted into the expected type by the {@link java.lang.reflect.Proxy}. * If the value and the expected type does not agree, and the value is not null, * a ClassCastException will be thrown. A NullPointerException will result if the * expected type is a primitive type and the value is null. * This behavior causes annotation processors a lot of pain and the decision is * to not throw such unchecked exception. In the case where a ClassCastException or * NullPointerException will be thrown return some dummy value. Otherwise, return * the original value. * Chosen dummy values: * Return <code>false</code> if the expected type is <code>boolean</code> * Return numeric 0 for all numeric primitive types and '0' for <code>char</code> * * This behavior is triggered by setting <code>avoidReflectException</code> to <code>true</code> * * Note: the new behavior deviates from what's documented in * {@link java.lang.reflect.InvocationHandler#invoke} and also deviates from * Sun's implementation. * * @param value the current value from the annotation instance. * @param expectedType the expected type of the value. * */ public static Object performNecessaryPrimitiveTypeConversion( final Class<?> expectedType, final Object value, final boolean avoidReflectException) { assert expectedType.isPrimitive() : "expectedType is not a primitive type: " + expectedType.getName(); //$NON-NLS-1$ if( value == null) return avoidReflectException ? getMatchingDummyValue(expectedType) : null; // apply widening conversion based on JLS 5.1.2 and 5.1.3 final String typeName = expectedType.getName(); final char expectedTypeChar = typeName.charAt(0); final int nameLen = typeName.length(); // widening byte -> short, int, long, float or double // narrowing byte -> char if( value instanceof Byte ) { final byte b = ((Byte)value).byteValue(); switch( expectedTypeChar ) { case 'b': if(nameLen == 4) // byte return value; // exact match. else return avoidReflectException ? Boolean.FALSE : value; case 'c': return new Character((char)b); // narrowing. case 'd': return new Double(b); // widening. case 'f': return new Float(b); // widening. case 'i': return new Integer(b); // widening. case 'l': return new Long(b); // widening. case 's': return new Short(b); // widening. default: throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$ } } // widening short -> int, long, float, or double // narrowing short -> byte or char else if( value instanceof Short ) { final short s = ((Short)value).shortValue(); switch( expectedTypeChar ) { case 'b': if(nameLen == 4) // byte return new Byte((byte)s); // narrowing. else return avoidReflectException ? Boolean.FALSE : value; // completely wrong. case 'c': return new Character((char)s); // narrowing. case 'd': return new Double(s); // widening. case 'f': return new Float(s); // widening. case 'i': return new Integer(s); // widening. case 'l': return new Long(s); // widening. case 's': return value; // exact match default: throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$ } } // widening char -> int, long, float, or double // narrowing char -> byte or short else if( value instanceof Character ) { final char c = ((Character)value).charValue(); switch( expectedTypeChar ) { case 'b': if(nameLen == 4) // byte return new Byte((byte)c); // narrowing. else return avoidReflectException ? Boolean.FALSE : value; // completely wrong. case 'c': return value; // exact match case 'd': return new Double(c); // widening. case 'f': return new Float(c); // widening. case 'i': return new Integer(c); // widening. case 'l': return new Long(c); // widening. case 's': return new Short((short)c); // narrowing. default: throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$ } } // widening int -> long, float, or double // narrowing int -> byte, short, or char else if( value instanceof Integer ) { final int i = ((Integer)value).intValue(); switch( expectedTypeChar ) { case 'b': if(nameLen == 4) // byte return new Byte((byte)i); // narrowing. else return avoidReflectException ? Boolean.FALSE : value; // completely wrong. case 'c': return new Character((char)i); // narrowing case 'd': return new Double(i); // widening. case 'f': return new Float(i); // widening. case 'i': return value; // exact match case 'l': return new Long(i); // widening. case 's': return new Short((short)i); // narrowing. default: throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$ } } // widening long -> float or double else if( value instanceof Long ) { final long l = ((Long)value).longValue(); switch( expectedTypeChar ) { case 'b': // both byte and boolean case 'c': case 'i': case 's': // completely wrong. return avoidReflectException ? getMatchingDummyValue(expectedType) : value; case 'd': return new Double(l); // widening. case 'f': return new Float(l); // widening. case 'l': return value; // exact match. default: throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$ } } // widening float -> double else if( value instanceof Float ) { final float f = ((Float)value).floatValue(); switch( expectedTypeChar ) { case 'b': // both byte and boolean case 'c': case 'i': case 's': case 'l': // completely wrong. return avoidReflectException ? getMatchingDummyValue(expectedType) : value; case 'd': return new Double(f); // widening. case 'f': return value; // exact match. default: throw new IllegalStateException("unknown type " + expectedTypeChar); //$NON-NLS-1$ } } else if( value instanceof Double ){ if(expectedTypeChar == 'd' ) return value; // exact match else{ return avoidReflectException ? getMatchingDummyValue(expectedType) : value; // completely wrong. } } else if( value instanceof Boolean ){ if( expectedTypeChar == 'b' && nameLen == 7) // "boolean".length() == 7 return value; else return avoidReflectException ? getMatchingDummyValue(expectedType) : value; // completely wrong. } else // can't convert return avoidReflectException ? getMatchingDummyValue(expectedType) : value; } /** * Set an element of an array to the appropriate dummy value type * @param array * @param i * @param expectedLeafType */ public static void setArrayMatchingDummyValue(Object array, int i, Class<?> expectedLeafType) { if (boolean.class.equals(expectedLeafType)) { Array.setBoolean(array, i, false); } else if (byte.class.equals(expectedLeafType)) { Array.setByte(array, i, DUMMY_BYTE); } else if (char.class.equals(expectedLeafType)) { Array.setChar(array, i, DUMMY_CHAR); } else if (double.class.equals(expectedLeafType)) { Array.setDouble(array, i, DUMMY_DOUBLE); } else if (float.class.equals(expectedLeafType)) { Array.setFloat(array, i, DUMMY_FLOAT); } else if (int.class.equals(expectedLeafType)) { Array.setInt(array, i, DUMMY_INTEGER); } else if (long.class.equals(expectedLeafType)) { Array.setLong(array, i, DUMMY_LONG); } else if (short.class.equals(expectedLeafType)) { Array.setShort(array, i, DUMMY_SHORT); } else { Array.set(array, i, null); } } /* Wrap repeating annotations into their container, return an array of bindings. Incoming array is not modified. */ public static AnnotationBinding [] getPackedAnnotationBindings(AnnotationBinding [] annotations) { int length = annotations == null ? 0 : annotations.length; if (length == 0) return annotations; AnnotationBinding[] repackagedBindings = annotations; // only replicate if repackaging. for (int i = 0; i < length; i++) { AnnotationBinding annotation = repackagedBindings[i]; if (annotation == null) continue; ReferenceBinding annotationType = annotation.getAnnotationType(); if (!annotationType.isRepeatableAnnotationType()) continue; ReferenceBinding containerType = annotationType.containerAnnotationType(); if (containerType == null) continue; // FUBAR. MethodBinding [] values = containerType.getMethods(TypeConstants.VALUE); if (values == null || values.length != 1) continue; // FUBAR. MethodBinding value = values[0]; if (value.returnType == null || value.returnType.dimensions() != 1 || TypeBinding.notEquals(value.returnType.leafComponentType(), annotationType)) continue; // FUBAR // We have a kosher repeatable annotation with a kosher containing type. See if actually repeats. List<AnnotationBinding> containees = null; for (int j = i + 1; j < length; j++) { AnnotationBinding otherAnnotation = repackagedBindings[j]; if (otherAnnotation == null) continue; if (otherAnnotation.getAnnotationType() == annotationType) { //$IDENTITY-COMPARISON$ if (repackagedBindings == annotations) System.arraycopy(repackagedBindings, 0, repackagedBindings = new AnnotationBinding[length], 0, length); repackagedBindings[j] = null; // so it is not double packed. if (containees == null) { containees = new ArrayList<AnnotationBinding>(); containees.add(annotation); } containees.add(otherAnnotation); } } if (containees != null) { ElementValuePair [] elementValuePairs = new ElementValuePair [] { new ElementValuePair(TypeConstants.VALUE, containees.toArray(), value) }; repackagedBindings[i] = new AnnotationBinding(containerType, elementValuePairs); } } if (repackagedBindings == annotations) return annotations; int finalTally = 0; for (int i = 0; i < length; i++) { if (repackagedBindings[i] != null) finalTally++; } annotations = new AnnotationBinding [finalTally]; for (int i = 0, j = 0; i < length; i++) { if (repackagedBindings[i] != null) annotations[j++] = repackagedBindings[i]; } return annotations; } /* Unwrap container annotations into the repeated annotations, return an array of bindings that includes the container and the containees. */ public static AnnotationBinding [] getUnpackedAnnotationBindings(AnnotationBinding [] annotations) { int length = annotations == null ? 0 : annotations.length; if (length == 0) return annotations; List<AnnotationBinding> unpackedAnnotations = new ArrayList<AnnotationBinding>(); for (int i = 0; i < length; i++) { AnnotationBinding annotation = annotations[i]; if (annotation == null) continue; unpackedAnnotations.add(annotation); ReferenceBinding annotationType = annotation.getAnnotationType(); MethodBinding [] values = annotationType.getMethods(TypeConstants.VALUE); if (values == null || values.length != 1) continue; MethodBinding value = values[0]; if (value.returnType.dimensions() != 1) continue; TypeBinding containeeType = value.returnType.leafComponentType(); if (containeeType == null || !containeeType.isAnnotationType() || !containeeType.isRepeatableAnnotationType()) continue; if (containeeType.containerAnnotationType() != annotationType) //$IDENTITY-COMPARISON$ continue; // We have a kosher container: unwrap the contained annotations. ElementValuePair [] elementValuePairs = annotation.getElementValuePairs(); for (ElementValuePair elementValuePair : elementValuePairs) { if (CharOperation.equals(elementValuePair.getName(), TypeConstants.VALUE)) { Object [] containees = (Object []) elementValuePair.getValue(); for (Object object : containees) { unpackedAnnotations.add((AnnotationBinding) object); } break; } } } return (AnnotationBinding[]) unpackedAnnotations.toArray(new AnnotationBinding [unpackedAnnotations.size()]); } }