blob: f64332a17d645102780fd3397e8df44ad457a7af [file] [log] [blame]
/*
* 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();
}
}