Java tutorial
/* * Grapht, an open source dependency injector. * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) * Copyright 2010-2014 Regents of the University of Minnesota * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This program 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 for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.grouplens.grapht.annotation; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.AnnotationUtils; import org.apache.commons.lang3.ClassUtils; import org.grouplens.grapht.util.ClassProxy; import java.io.*; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; /** * Proxy used to implement annotation interfaces. It implements the {@link Annotation} * contract by delegating to a map of named attribute values. A new AnnotationProxy instance * should be created for each proxy annotation. * * @see AnnotationBuilder */ class AnnotationProxy<T extends Annotation> implements InvocationHandler, Serializable { private static final long serialVersionUID = 1L; private final ClassProxy annotationType; private final ImmutableMap<String, Object> attributes; private transient Class<T> cachedType; public AnnotationProxy(Class<T> type, Map<String, Object> attrs) { annotationType = ClassProxy.of(type); cachedType = type; attributes = ImmutableMap.copyOf(attrs); } /** * Customized {@code readObject} implementation to ensure the cached type is resolved. * * @param in The stream. * @throws java.io.ObjectStreamException If there is an error reading the object from the stream. */ @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws ObjectStreamException { try { in.defaultReadObject(); cachedType = (Class<T>) annotationType.resolve(); } catch (IOException e) { ObjectStreamException ex = new StreamCorruptedException("IO exception"); ex.initCause(e); throw ex; } catch (ClassNotFoundException e) { ObjectStreamException ex = new InvalidObjectException("IO exception"); ex.initCause(e); throw ex; } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (isHashCode(method)) { return proxyHashCode(proxy); } else if (isEquals(method)) { return proxyEquals(proxy, args[0]); } else if (isAnnotationType(method)) { return proxyAnnotationType(); } else if (isToString(method)) { return proxyToString(proxy); } else if (attributes.containsKey(method.getName()) && method.getParameterTypes().length == 0) { return copyAnnotationValue(attributes.get(method.getName())); } else { // fall back to the default return copyAnnotationValue(method.getDefaultValue()); } // wait() and other Object methods do not get sent to the InvocationHandler // so we don't have any other cases } private boolean isEquals(Method m) { return m.getName().equals("equals") && m.getReturnType().equals(boolean.class) && m.getParameterTypes().length == 1 && m.getParameterTypes()[0].equals(Object.class); } private boolean isHashCode(Method m) { return m.getName().equals("hashCode") && m.getReturnType().equals(int.class) && m.getParameterTypes().length == 0; } private boolean isAnnotationType(Method m) { return m.getName().equals("annotationType") && m.getReturnType().equals(Class.class) && m.getParameterTypes().length == 0; } private boolean isToString(Method m) { return m.getName().equals("toString") && m.getReturnType().equals(String.class) && m.getParameterTypes().length == 0; } private Class<? extends Annotation> proxyAnnotationType() { return cachedType; } private String proxyToString(Object o) { return AnnotationUtils.toString((Annotation) o); } private int proxyHashCode(Object proxy) { return AnnotationUtils.hashCode((Annotation) proxy); } private boolean proxyEquals(Object o1, Object o2) { return AnnotationUtils.equals((Annotation) o1, (Annotation) o2); } /** * Safe clone of an object. If the object is an array, it is copied; otherwise, it is * returned as-is. This object is only applicable to valid annotation value types, which * are all either arrays or immutable. * @param o The annotation value. * @return A copy of the value. */ @SuppressWarnings("unchecked") static Object copyAnnotationValue(Object o) { if (o.getClass().isArray()) { // make a shallow copy of the array if (o instanceof boolean[]) { boolean[] a = (boolean[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof byte[]) { byte[] a = (byte[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof short[]) { short[] a = (short[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof int[]) { int[] a = (int[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof long[]) { long[] a = (long[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof char[]) { char[] a = (char[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof float[]) { float[] a = (float[]) o; return Arrays.copyOf(a, a.length); } else if (o instanceof double[]) { double[] a = (double[]) o; return Arrays.copyOf(a, a.length); } else { Object[] a = (Object[]) o; return Arrays.copyOf(a, a.length, (Class<? extends Object[]>) o.getClass()); } } else if (o instanceof String || o instanceof Annotation || ClassUtils.isPrimitiveOrWrapper(o.getClass()) || o instanceof Enum || o instanceof Class) { // the value is immutable and a copy is not necessary return o; } else { throw new IllegalArgumentException("not an annotation value"); } } }