org.grouplens.grapht.annotation.AnnotationProxy.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.grapht.annotation.AnnotationProxy.java

Source

/*
 * 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");
        }
    }
}