/**
* Copyright (C) 2010 Lowereast Software
*
* Licensed 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 com.lowereast.guiceymongo.annotation;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.inject.internal.Lists;
import com.google.inject.internal.Maps;
public class Annotations {
private static class AnnotationProxy<T extends Annotation> implements InvocationHandler {
private final Class<T> _annotationClass;
private final List<String> _elements = Lists.newArrayList();
private final Map<String, Object> _values;
private final Map<String, Method> _methods = Maps.newHashMap();
public AnnotationProxy(Class<T> annotationClass, Map<String, Object> values) {
_values = (values == null ? Maps.<String, Object>newHashMap() : new HashMap<String, Object>(values));
_annotationClass = annotationClass;
for (Method method : _annotationClass.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers()) && Modifier.isAbstract(method.getModifiers()) && method.getParameterTypes().length == 0) {
String elementName = method.getName();
_elements.add(elementName);
_methods.put(elementName, method);
Object defaultValue = method.getDefaultValue();
if (defaultValue != null && !_values.containsKey(elementName))
_values.put(elementName, defaultValue);
}
}
}
private static boolean isEqualToAny(Object lhs, Object... rhs) {
for (Object item : rhs) {
if (lhs.equals(item))
return true;
}
return false;
}
private int valueHashCode(Class<?> elementType, Object value) {
if (elementType.isArray()) {
return Arrays.hashCode((Object[])value);
} else if (isEqualToAny(elementType,
byte.class, Byte.class,
char.class, Character.class,
double.class, Double.class,
float.class, Float.class,
int.class, Integer.class,
long.class, Long.class,
short.class, Short.class,
boolean.class, Boolean.class,
String.class)
|| elementType.isEnum()
|| elementType.isAnnotation()) {
return value.hashCode();
}
throw new RuntimeException("This shouldn't happen");
}
private int calculateHashCode() {
// This is specified in java.lang.Annotation.
int hashCode = 0;
for (String element : _elements)
hashCode += (127 * element.hashCode()) ^ valueHashCode(_methods.get(element).getReturnType(), _values.get(element));
return hashCode;
}
private boolean checkEquals(Object other) throws Throwable {
if (!_annotationClass.isAssignableFrom(other.getClass()))
return false;
for (String element : _elements) {
Method method = _methods.get(element);
method.setAccessible(true);
Object value = _values.get(element);
Object otherValue = method.invoke(other);
if (!value.equals(otherValue))
return false;
}
return true;
}
private String buildString() {
StringBuilder builder = new StringBuilder("@").append(_annotationClass.getName());
if (_elements.size() > 0) {
builder.append("(");
for (int x = 0; x < _elements.size(); ++x) {
if (x > 0)
builder.append(",");
builder.append(_elements.get(x)).append("=").append(_values.get(_elements.get(x)));
}
builder.append(")");
}
return builder.toString();
}
// http://java.sun.com/j2se/1.5.0/docs/api/java/lang/annotation/Annotation.html
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("annotationType".equals(methodName)) {
return _annotationClass;
} else if ("equals".equals(methodName)) {
return checkEquals(args[0]);
} else if ("hashCode".equals(methodName)) {
return calculateHashCode();
} else if ("toString".equals(methodName)) {
return buildString();
} else if (_elements.contains(methodName)) {
return _values.get(methodName);
}
throw new RuntimeException();
}
}
public static <T extends Annotation> T proxy(Class<T> annotationClass) {
return proxy(annotationClass, null);
}
@SuppressWarnings("unchecked")
public static <T extends Annotation> T proxy(Class<T> annotationClass, Map<String, Object> values) {
return (T)Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[] { annotationClass, Serializable.class }, new AnnotationProxy(annotationClass, values));
}
}