| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.harmony.lang.annotation; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.lang.annotation.AnnotationTypeMismatchException; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| |
| /** |
| * This class represents member element of an annotation. |
| * It consists of name and value, supplemented with element |
| * definition information (such as declared type of element). |
| * <br>The value may be one of the following types: |
| * <ul> |
| * <li> boxed primitive |
| * <li> Class |
| * <li> enum constant |
| * <li> annotation (nested) |
| * <li> one-dimensional array of the above |
| * <li> Throwable |
| * </ul> |
| * The last type is specific for this implementation; a Throwable value |
| * means that the error occured during parsing or resolution of corresponding |
| * class-data structures and throwing is delayed until the element |
| * is requested for value. |
| * |
| * @see android.lang.annotation.AnnotationFactory |
| * |
| * @author Alexey V. Varlamov, Serguei S. Zapreyev |
| * @version $Revision$ |
| */ |
| @SuppressWarnings({"serial"}) |
| public class AnnotationMember implements Serializable { |
| |
| /** |
| * Tag description of a Throwable value type. |
| */ |
| protected static final char ERROR = '!'; |
| |
| /** |
| * Tag description of an array value type. |
| */ |
| protected static final char ARRAY = '['; |
| |
| /** |
| * Tag description of all value types except arrays and Throwables. |
| */ |
| protected static final char OTHER = '*'; |
| |
| // public static final char INT = 'I'; |
| // public static final char CHAR = 'C'; |
| // public static final char DOUBLE = 'D'; |
| // public static final char FLOAT = 'F'; |
| // public static final char BYTE = 'B'; |
| // public static final char LONG = 'J'; |
| // public static final char SHORT = 'S'; |
| // public static final char BOOL = 'Z'; |
| // public static final char CLASS = 'c'; |
| // public static final char ENUM = 'e'; |
| // public static final char ANTN = '@'; |
| |
| private enum DefaultValues {NO_VALUE} |
| |
| /** |
| * Singleton representing missing element value. |
| */ |
| protected static final Object NO_VALUE = DefaultValues.NO_VALUE; |
| |
| protected final String name; |
| protected final Object value; // a primitive value is wrapped to the corresponding wrapper class |
| protected final char tag; |
| // no sense to serialize definition info as it can be changed arbitrarily |
| protected transient Class<?> elementType; |
| protected transient Method definingMethod; |
| |
| |
| /** |
| * Creates a new element with specified name and value. |
| * Definition info will be provided later when this |
| * element becomes actual annotation member. |
| * @param name element name, must not be null |
| * @param val element value, should be of addmissible type, |
| * as specified in the description of this class |
| * |
| * @see #setDefinition(AnnotationMember) |
| */ |
| public AnnotationMember(String name, Object val) { |
| this.name = name; |
| value = val == null ? NO_VALUE : val; |
| if (value instanceof Throwable) { |
| tag = ERROR; |
| } else if (value.getClass().isArray()) { |
| tag = ARRAY; |
| } else { |
| tag = OTHER; |
| } |
| } |
| |
| /** |
| * Creates the completely defined element. |
| * @param name element name, must not be null |
| * @param value element value, should be of addmissible type, |
| * as specified in the description of this class |
| * @param m element-defining method, reflected on the annotation type |
| * @param type declared type of this element |
| * (return type of the defining method) |
| */ |
| public AnnotationMember(String name, Object val, Class type, Method m) { |
| this(name, val); |
| |
| definingMethod = m; |
| |
| if (type == int.class) { |
| elementType = Integer.class; |
| } else if (type == boolean.class) { |
| elementType = Boolean.class; |
| } else if (type == char.class) { |
| elementType = Character.class; |
| } else if (type == float.class) { |
| elementType = Float.class; |
| } else if (type == double.class) { |
| elementType = Double.class; |
| } else if (type == long.class) { |
| elementType = Long.class; |
| } else if (type == short.class) { |
| elementType = Short.class; |
| } else if (type == byte.class) { |
| elementType = Byte.class; |
| } else { |
| elementType = type; |
| } |
| } |
| |
| /** |
| * Fills in element's definition info and returns this. |
| */ |
| protected AnnotationMember setDefinition(AnnotationMember copy) { |
| definingMethod = copy.definingMethod; |
| elementType = copy.elementType; |
| return this; |
| } |
| |
| /** |
| * Returns readable description of this annotation value. |
| */ |
| public String toString() { |
| if (tag == ARRAY) { |
| StringBuilder sb = new StringBuilder(80); |
| sb.append(name).append("=["); |
| int len = Array.getLength(value); |
| for (int i = 0; i < len; i++) { |
| if (i != 0) sb.append(", "); |
| sb.append(Array.get(value, i)); |
| } |
| return sb.append("]").toString(); |
| } else { |
| return name+ "=" +value; |
| } |
| } |
| |
| /** |
| * Returns true if the specified object represents equal element |
| * (equivalent name-value pair). |
| * <br> A special case is the contained Throwable value; it is considered |
| * transcendent so no other element would be equal. |
| * @return true if passed object is equivalent element representation, |
| * false otherwise |
| * @see #equalArrayValue(Object) |
| * @see java.lang.annotation.Annotation#equals(Object) |
| */ |
| public boolean equals(Object obj) { |
| if (obj == this) { |
| // not a mere optimization, |
| // this is needed for consistency with hashCode() |
| return true; |
| } |
| if (obj instanceof AnnotationMember) { |
| AnnotationMember that = (AnnotationMember)obj; |
| if (name.equals(that.name) && tag == that.tag) { |
| if (tag == ARRAY) { |
| return equalArrayValue(that.value); |
| } else if (tag == ERROR) { |
| // undefined value is incomparable (transcendent) |
| return false; |
| } else { |
| return value.equals(that.value); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if the contained value and a passed object are equal arrays, |
| * false otherwise. Appropriate overloaded method of Arrays.equals() |
| * is used for equality testing. |
| * @see java.util.Arrays#equals(java.lang.Object[], java.lang.Object[]) |
| * @return true if the value is array and is equal to specified object, |
| * false otherwise |
| */ |
| public boolean equalArrayValue(Object otherValue) { |
| if (value instanceof Object[] && otherValue instanceof Object[]) { |
| return Arrays.equals((Object[])value, (Object[])otherValue); |
| } |
| Class type = value.getClass(); |
| if (type != otherValue.getClass()) { |
| return false; |
| } |
| if (type == int[].class) { |
| return Arrays.equals((int[])value, (int[])otherValue); |
| } else if (type == byte[].class) { |
| return Arrays.equals((byte[])value, (byte[])otherValue); |
| } else if (type == short[].class) { |
| return Arrays.equals((short[])value, (short[])otherValue); |
| } else if (type == long[].class) { |
| return Arrays.equals((long[])value, (long[])otherValue); |
| } else if (type == char[].class) { |
| return Arrays.equals((char[])value, (char[])otherValue); |
| } else if (type == boolean[].class) { |
| return Arrays.equals((boolean[])value, (boolean[])otherValue); |
| } else if (type == float[].class) { |
| return Arrays.equals((float[])value, (float[])otherValue); |
| } else if (type == double[].class) { |
| return Arrays.equals((double[])value, (double[])otherValue); |
| } |
| return false; |
| } |
| |
| /** |
| * Computes hash code of this element. The formula is as follows: |
| * <code> (name.hashCode() * 127) ^ value.hashCode() </code> |
| * <br>If value is an array, one of overloaded Arrays.hashCode() |
| * methods is used. |
| * @return the hash code |
| * @see java.util.Arrays#hashCode(java.lang.Object[]) |
| * @see java.lang.annotation.Annotation#hashCode() |
| */ |
| public int hashCode() { |
| int hash = name.hashCode() * 127; |
| if (tag == ARRAY) { |
| Class type = value.getClass(); |
| if (type == int[].class) { |
| return hash ^ Arrays.hashCode((int[])value); |
| } else if (type == byte[].class) { |
| return hash ^ Arrays.hashCode((byte[])value); |
| } else if (type == short[].class) { |
| return hash ^ Arrays.hashCode((short[])value); |
| } else if (type == long[].class) { |
| return hash ^ Arrays.hashCode((long[])value); |
| } else if (type == char[].class) { |
| return hash ^ Arrays.hashCode((char[])value); |
| } else if (type == boolean[].class) { |
| return hash ^ Arrays.hashCode((boolean[])value); |
| } else if (type == float[].class) { |
| return hash ^ Arrays.hashCode((float[])value); |
| } else if (type == double[].class) { |
| return hash ^ Arrays.hashCode((double[])value); |
| } |
| return hash ^ Arrays.hashCode((Object[])value); |
| } else { |
| return hash ^ value.hashCode(); |
| } |
| } |
| |
| /** |
| * Throws contained error (if any) with a renewed stack trace. |
| */ |
| public void rethrowError() throws Throwable { |
| if (tag == ERROR) { |
| // need to throw cloned exception for thread safety |
| // besides it is better to provide actual stack trace |
| // rather than recorded during parsing |
| |
| // first check for expected types |
| if (value instanceof TypeNotPresentException) { |
| TypeNotPresentException tnpe = (TypeNotPresentException)value; |
| throw new TypeNotPresentException(tnpe.typeName(), tnpe.getCause()); |
| } else if (value instanceof EnumConstantNotPresentException) { |
| EnumConstantNotPresentException ecnpe = (EnumConstantNotPresentException)value; |
| throw new EnumConstantNotPresentException(ecnpe.enumType(), ecnpe.constantName()); |
| } else if (value instanceof ArrayStoreException) { |
| ArrayStoreException ase = (ArrayStoreException)value; |
| throw new ArrayStoreException(ase.getMessage()); |
| } |
| // got some other error, have to go with deep cloning |
| // via serialization mechanism |
| Throwable error = (Throwable)value; |
| StackTraceElement[] ste = error.getStackTrace(); |
| ByteArrayOutputStream bos = new ByteArrayOutputStream( |
| ste == null ? 512 : (ste.length + 1) * 80); |
| ObjectOutputStream oos = new ObjectOutputStream(bos); |
| oos.writeObject(error); |
| oos.flush(); |
| oos.close(); |
| ByteArrayInputStream bis = new ByteArrayInputStream(bos |
| .toByteArray()); |
| ObjectInputStream ois = new ObjectInputStream(bis); |
| error = (Throwable)ois.readObject(); |
| ois.close(); |
| |
| throw error; |
| } |
| } |
| |
| /** |
| * Validates contained value against its member definition |
| * and if ok returns the value. |
| * Otherwise, if the value type mismatches definition |
| * or the value itself describes an error, |
| * throws appropriate exception. |
| * <br> Note, this method may return null if this element was constructed |
| * with such value. |
| * |
| * @see #rethrowError() |
| * @see #copyValue() |
| * @return actual valid value or null if no value |
| */ |
| public Object validateValue() throws Throwable { |
| if (tag == ERROR) { |
| rethrowError(); |
| } |
| if (value == NO_VALUE) { |
| return null; |
| } |
| if (elementType == value.getClass() |
| || elementType.isInstance(value)) { // nested annotation value |
| return copyValue(); |
| } else { |
| throw new AnnotationTypeMismatchException(definingMethod, |
| value.getClass().getName()); |
| } |
| |
| } |
| |
| |
| /** |
| * Provides mutation-safe access to contained value. That is, caller is free |
| * to modify the returned value, it will not affect the contained data value. |
| * @return cloned value if it is mutable or the original immutable value |
| */ |
| public Object copyValue() throws Throwable |
| { |
| if (tag != ARRAY || Array.getLength(value) == 0) { |
| return value; |
| } |
| Class type = value.getClass(); |
| if (type == int[].class) { |
| return ((int[])value).clone(); |
| } else if (type == byte[].class) { |
| return ((byte[])value).clone(); |
| } else if (type == short[].class) { |
| return ((short[])value).clone(); |
| } else if (type == long[].class) { |
| return ((long[])value).clone(); |
| } else if (type == char[].class) { |
| return ((char[])value).clone(); |
| } else if (type == boolean[].class) { |
| return ((boolean[])value).clone(); |
| } else if (type == float[].class) { |
| return ((float[])value).clone(); |
| } else if (type == double[].class) { |
| return ((double[])value).clone(); |
| } |
| return ((Object[])value).clone(); |
| } |
| } |