blob: 3fd13f5c93f43eb82a6dd3c43676c504d461df60 [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.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import static org.apache.harmony.lang.annotation.AnnotationMember.ARRAY;
import static org.apache.harmony.lang.annotation.AnnotationMember.ERROR;
/**
* The annotation implementation based on dynamically generated proxy instances.
* It conforms to all requirements stated in public APIs, see in particular
* {@link java.lang.reflect.AnnotatedElement java.lang.reflect.AnnotatedElement}
* and {@link java.lang.annotation.Annotation java.lang.annotation.Annotation}.
* Namely, annotation instances are immutable and serializable; they provide
* conforming access to annotation member values and required implementations of
* methods declared in Annotation interface.
*
* @see android.lang.annotation.AnnotationMember
* @see java.lang.annotation.Annotation
*
* @author Alexey V. Varlamov, Serguei S. Zapreyev
* @version $Revision$
*/
@SuppressWarnings({"serial"})
public final class AnnotationFactory implements InvocationHandler, Serializable {
private static final transient
Map<Class<? extends Annotation>, AnnotationMember[]>
cache = new WeakHashMap<Class<? extends Annotation>, AnnotationMember[]>();
/**
* Reflects specified annotation type and returns an array
* of member element definitions with default values.
*/
public static AnnotationMember[] getElementsDescription(Class<? extends Annotation> annotationType ) {
AnnotationMember[] desc = cache.get(annotationType);
if (desc == null) {
if (!annotationType.isAnnotation()) {
throw new IllegalArgumentException("Type is not annotation: "
+ annotationType.getName());
}
Method[] m = annotationType.getDeclaredMethods();
desc = new AnnotationMember[m.length];
int idx = 0;
for (Method element : m) {
String name = element.getName();
Class<?> type = element.getReturnType();
try {
desc[idx] = new AnnotationMember(name,
element.getDefaultValue(), type, element);
} catch (Throwable t) {
desc[idx] = new AnnotationMember(name, t, type, element);
}
idx++;
}
cache.put(annotationType, desc);
}
return desc;
}
/**
* Provides a new annotation instance.
* @param annotationType the annotation type definition
* @param elements name-value pairs representing elements of the annotation
* @return a new annotation instance
*/
public static Annotation createAnnotation(
Class<? extends Annotation> annotationType,
AnnotationMember[] elements)
{
AnnotationFactory antn = new AnnotationFactory(annotationType, elements);
return (Annotation)Proxy.newProxyInstance( annotationType.getClassLoader(),
new Class[]{annotationType}, antn);
}
private final Class<? extends Annotation> klazz;
private AnnotationMember[] elements;
/**
* New instances should not be created directly, use factory method
* {@link #createAnnotation(Class, AnnotationMember[]) createAnnotation()}
* instead.
*
* @param klzz class defining the annotation type
* @param values actual element values
*/
private AnnotationFactory(Class<? extends Annotation> klzz, AnnotationMember[] values) {
klazz = klzz;
AnnotationMember[] defs = getElementsDescription(klazz);
if (values == null) {
elements = defs;
} else {
//merge default and actual values
elements = new AnnotationMember[defs.length];
next: for (int i = elements.length - 1; i >= 0; i-- ){
for (AnnotationMember val : values){
if (val.name.equals(defs[i].name)) {
elements[i] = val.setDefinition(defs[i]);
continue next;
}
}
elements[i] = defs[i];
}
}
}
/**
* Reads the object, obtains actual member definitions for the annotation type,
* and merges deserialized values with the new definitions.
*/
private void readObject(ObjectInputStream os) throws IOException,
ClassNotFoundException {
os.defaultReadObject();
// Annotation type members can be changed arbitrarily
// So there may be zombi elements from the previous life;
// they hardly fit into this new annotation's incarnation,
// as we have no defining methods for them.
// Reasonably just drop such elements,
// but seems better to keep them for compatibility
AnnotationMember[] defs = getElementsDescription(klazz);
AnnotationMember[] old = elements;
List<AnnotationMember> merged = new ArrayList<AnnotationMember>(
defs.length + old.length);
nextOld: for (AnnotationMember el1 : old) {
for (AnnotationMember el2 : defs) {
if (el2.name.equals(el1.name)) {
continue nextOld;
}
}
merged.add(el1); //phantom element
}
nextNew: for (AnnotationMember def : defs){
for (AnnotationMember val : old){
if (val.name.equals(def.name)) {
// nothing to do about cached errors (if any)
// anyway they remain relevant to values
merged.add(val.setDefinition(def));
continue nextNew;
}
}
merged.add(def); // brand new element
}
elements = merged.toArray(new AnnotationMember[merged.size()]);
}
/**
* Returns true if the specified object represents the same annotation instance.
* That is, if it implements the same annotation type and
* returns the same element values.
* <br>Note, actual underlying implementation mechanism does not matter - it may
* differ completely from this class.
* @return true if the passed object is equivalent annotation instance,
* false otherwise.
* @see android.lang.annotation.AnnotationMember#equals(Object)
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!klazz.isInstance(obj)) {
return false;
}
Object handler = null;
if (Proxy.isProxyClass(obj.getClass())
&& (handler = Proxy.getInvocationHandler(obj)) instanceof AnnotationFactory ) {
AnnotationFactory other = (AnnotationFactory) handler;
if (elements.length != other.elements.length) {
return false;
}
next: for (AnnotationMember el1 : elements){
for (AnnotationMember el2 : other.elements) {
if (el1.equals(el2)) {
continue next;
}
}
return false;
}
return true;
} else {
// encountered foreign annotation implementaton
// so have to obtain element values via invocation
// of corresponding methods
for (final AnnotationMember el : elements) {
if (el.tag == ERROR) {
// undefined value is incomparable (transcendent)
return false;
}
try {
if (!el.definingMethod.isAccessible()) {
el.definingMethod.setAccessible(true);
}
Object otherValue = el.definingMethod.invoke(obj);
if (otherValue != null ) {
if (el.tag == ARRAY) {
if (!el.equalArrayValue(otherValue)) {
return false;
}
} else {
if (!el.value.equals(otherValue)) {
return false;
}
}
} else if (el.value != AnnotationMember.NO_VALUE) {
return false;
}
} catch (Throwable e) {
return false;
}
}
return true;
}
}
/**
* Returns a hash code composed as a sum of hash codes of member elements,
* including elements with default values.
* @see android.lang.annotation.AnnotationMember#hashCode()
*/
public int hashCode() {
int hash = 0;
for (AnnotationMember element : elements) {
hash += element.hashCode();
}
return hash;
}
/**
* Provides detailed description of this annotation instance,
* including all member name-values pairs.
* @return string representation of this annotation
*/
public String toString() {
StringBuilder result = new StringBuilder();
result.append('@');
result.append(klazz.getName());
result.append('(');
for (int i = 0; i < elements.length; ++i) {
if (i != 0) {
result.append(", ");
}
result.append(elements[i]);
}
result.append(')');
return result.toString();
}
/**
* Processes a method invocation request to this annotation instance.
* Recognizes the methods declared in the
* {@link java.lang.annotation.Annotation java.lang.annotation.Annotation}
* interface, and member-defining methods of the implemented annotation type.
* @throws IllegalArgumentException If the specified method is none of the above
* @return the invocation result
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
String name = method.getName();
Class[] params = method.getParameterTypes();
if (params.length == 0) {
if ("annotationType".equals(name)) {
return klazz;
} else if ("toString".equals(name)) {
return toString();
} else if ("hashCode".equals(name)) {
return hashCode();
}
// this must be element value request
AnnotationMember element = null;
for (AnnotationMember el : elements) {
if (name.equals(el.name)) {
element = el;
break;
}
}
if (element == null || !method.equals(element.definingMethod)) {
throw new IllegalArgumentException(method.toString());
} else {
Object value = element.validateValue();
if (value == null) {
throw new IncompleteAnnotationException(klazz, name);
}
return value;
}
} else if (params.length == 1 && params[0] == Object.class && "equals".equals(name)){
return Boolean.valueOf(equals(args[0]));
}
throw new IllegalArgumentException(
"Invalid method for annotation type: " + method);
}
}