/* * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 David Berkman * * This file is part of the SmallMind Code Project. * * The SmallMind Code Project is free software, you can redistribute * it and/or modify it under either, at your discretion... * * 1) The terms of GNU Affero General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * ...or... * * 2) The terms of the Apache License, Version 2.0. * * The SmallMind Code Project is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License or Apache License for more details. * * You should have received a copy of the GNU Affero General Public License * and the Apache License along with the SmallMind Code Project. If not, see * <http://www.gnu.org/licenses/> or <http://www.apache.org/licenses/LICENSE-2.0>. * * Additional permission under the GNU Affero GPL version 3 section 7 * ------------------------------------------------------------------ * If you modify this Program, or any covered work, by linking or * combining it with other code, such other code is not for that reason * alone subject to any of the requirements of the GNU Affero GPL * version 3. */ package org.smallmind.nutsnbolts.lang; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; public abstract class AnnotationLiteral<A extends Annotation> implements Annotation, Serializable { private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private Class<A> annotationType; protected AnnotationLiteral () { this.annotationType = getAnnotationType(getClass()); } public Class<? extends Annotation> annotationType () { return annotationType; } private Class<A> getAnnotationType (Class<?> definedClazz) { Type superClazz = definedClazz.getGenericSuperclass(); Class<A> clazz; if (superClazz.equals(Object.class)) { throw new RuntimeException("Super class must be A parametrized type!"); } else if (superClazz instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType)superClazz; Type[] actualArgs = paramType.getActualTypeArguments(); if (actualArgs.length == 1) { //Actual annotation type Type type = actualArgs[0]; if (type instanceof Class) { clazz = (Class<A>)type; return clazz; } else { throw new RuntimeException("Not a class type!"); } } else { throw new RuntimeException("More than one parametric type!"); } } else { return getAnnotationType((Class<?>)superClazz); } } @Override public boolean equals (Object other) { Method[] methods = (Method[])AccessController.doPrivileged(new PrivilegedAction() { public Object run () { return annotationType.getDeclaredMethods(); } }); if (other == this) { return true; } if (other == null) { return false; } if (other instanceof Annotation) { Annotation annotOther = (Annotation)other; if (this.annotationType().equals(annotOther.annotationType())) { for (Method method : methods) { Object value = callMethod(this, method); Object annotValue = callMethod(annotOther, method); if ((value == null && annotValue != null) || (value != null && annotValue == null)) { return false; } if (value == null && annotValue == null) { continue; } Class<?> valueClass = value.getClass(); Class<?> annotValueClass = annotValue.getClass(); if (valueClass.isPrimitive() && annotValueClass.isPrimitive()) { if ((valueClass != Float.TYPE && annotValue != Float.TYPE) || (valueClass != Double.TYPE && annotValue != Double.TYPE)) { if (value != annotValue) { return false; } } } else if (valueClass.isArray() && annotValueClass.isArray()) { Class<?> type = valueClass.getComponentType(); if (type.isPrimitive()) { if (Long.TYPE == type) { if (!Arrays.equals(((Long[])value), (Long[])annotValue)) { return false; } } else if (Integer.TYPE == type) { if (!Arrays.equals(((Integer[])value), (Integer[])annotValue)) { return false; } } else if (Short.TYPE == type) { if (!Arrays.equals(((Short[])value), (Short[])annotValue)) { return false; } } else if (Double.TYPE == type) { if (!Arrays.equals(((Double[])value), (Double[])annotValue)) { return false; } } else if (Float.TYPE == type) { if (!Arrays.equals(((Float[])value), (Float[])annotValue)) { return false; } } else if (Boolean.TYPE == type) { if (!Arrays.equals(((Boolean[])value), (Boolean[])annotValue)) { return false; } } else if (Byte.TYPE == type) { if (!Arrays.equals(((Byte[])value), (Byte[])annotValue)) { return false; } } else if (Character.TYPE == type) { if (!Arrays.equals(((Character[])value), (Character[])annotValue)) { return false; } } } else { if (!Arrays.equals(((Object[])value), (Object[])annotValue)) { return false; } } } else if (value != null && annotValue != null) { if (!value.equals(annotValue)) { return false; } } } return true; } } return false; } private Object callMethod (Object instance, Method method) { boolean access = method.isAccessible(); try { if (!method.isAccessible()) { AccessController.doPrivileged(new PrivilegedActionForAccessibleObject(method, true)); } return method.invoke(instance, EMPTY_OBJECT_ARRAY); } catch (Exception e) { throw new RuntimeException("Exception in method call : " + method.getName(), e); } finally { AccessController.doPrivileged(new PrivilegedActionForAccessibleObject(method, access)); } } @Override public int hashCode () { Method[] methods = (Method[])AccessController.doPrivileged(new PrivilegedAction() { public Object run () { return annotationType.getDeclaredMethods(); } }); int hashCode = 0; for (Method method : methods) { // Member name int name = 127 * method.getName().hashCode(); // Member value Object object = callMethod(this, method); int value = 0; if (object.getClass().isArray()) { Class<?> type = object.getClass().getComponentType(); if (type.isPrimitive()) { if (Long.TYPE == type) { value = Arrays.hashCode((Long[])object); } else if (Integer.TYPE == type) { value = Arrays.hashCode((Integer[])object); } else if (Short.TYPE == type) { value = Arrays.hashCode((Short[])object); } else if (Double.TYPE == type) { value = Arrays.hashCode((Double[])object); } else if (Float.TYPE == type) { value = Arrays.hashCode((Float[])object); } else if (Boolean.TYPE == type) { value = Arrays.hashCode((Long[])object); } else if (Byte.TYPE == type) { value = Arrays.hashCode((Byte[])object); } else if (Character.TYPE == type) { value = Arrays.hashCode((Character[])object); } } else { value = Arrays.hashCode((Object[])object); } } else { value = object.hashCode(); } hashCode += name ^ value; } return hashCode; } @Override public String toString () { Method[] methods = (Method[])AccessController.doPrivileged(new PrivilegedAction() { public Object run () { return annotationType.getDeclaredMethods(); } }); StringBuilder sb = new StringBuilder("@" + annotationType().getName() + "("); int lenght = methods.length; for (int i = 0; i < lenght; i++) { // Member name sb.append(methods[i].getName()).append("="); // Member value sb.append(callMethod(this, methods[i])); if (i < lenght - 1) { sb.append(","); } } sb.append(")"); return sb.toString(); } protected static class PrivilegedActionForAccessibleObject implements PrivilegedAction<Object> { AccessibleObject object; boolean flag; protected PrivilegedActionForAccessibleObject (AccessibleObject object, boolean flag) { this.object = object; this.flag = flag; } public Object run () { object.setAccessible(flag); return null; } } }