GenericClass.java Source code

Java tutorial

Introduction

Here is the source code for GenericClass.java

Source

/*
 * 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.
 */

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * A wrapper around reflection to resolve generics.
 * 
 * @author Simone Gianni <simoneg@apache.org>
 */
// TODO there is vast space for caching and optimizations here!
public class GenericClass {

    private Class<?> myclass = null;

    private Map<String, Class<?>> genericMap = new HashMap<String, Class<?>>();

    private Map<String, String> reverseIntermediate = new HashMap<String, String>();

    public static GenericClass forClass(Class<?> concrete) {
        return new GenericClass(concrete);
    }

    public static GenericClass forField(Field field) {
        return forGenericType(field.getGenericType());
    }

    public static GenericClass forReturnType(Method method) {
        return forGenericType(method.getGenericReturnType());
    }

    public static GenericClass forParameter(Method method, int index) {
        return forGenericType(method.getGenericParameterTypes()[index]);
    }

    public static GenericClass forGenericType(Type type) {
        if (type instanceof Class) {
            return forClass((Class<?>) type);
        } else if (type instanceof ParameterizedType) {
            return new GenericClass((ParameterizedType) type);
        } else {
            return forClass(Object.class);
            // throw new MagmaException("Dont know how to build a GenericClass out of
            // {0}", type.getClass());
        }
    }

    private GenericClass(Class<?> concrete) {
        TypeVariable<?>[] parameters = concrete.getTypeParameters();
        // if (parameters.length > 0) throw new MagmaException("Cannot parse {0}, it
        // is a generic class, use a concrete class instead", concrete);
        myclass = concrete;
        recurse(concrete);
        coalesceMap();
    }

    private GenericClass(ParameterizedType type) {
        myclass = (Class<?>) type.getRawType();
        recurse(myclass, myclass, type);
        coalesceMap();
    }

    private void coalesceMap() {
        int cnt = reverseIntermediate.size();
        while (reverseIntermediate.size() > 0 && cnt > 0) {
            for (Iterator<Map.Entry<String, String>> iterator = this.reverseIntermediate.entrySet()
                    .iterator(); iterator.hasNext();) {
                Map.Entry<String, String> entry = iterator.next();
                String target = entry.getValue();
                String search = entry.getKey();
                Class<?> clazz = genericMap.get(search);
                if (clazz == null)
                    continue;
                if (genericMap.containsKey(target))
                    throw new RuntimeException(
                            "Impossible situation, it's a bug : {0} generic is both {1} and bound to {2}" + target
                                    + genericMap.get(target) + search);
                genericMap.put(target, clazz);
                iterator.remove();
            }
            cnt--;
        }
        if (reverseIntermediate.size() > 0) {
            for (Iterator<Map.Entry<String, String>> iterator = this.reverseIntermediate.entrySet()
                    .iterator(); iterator.hasNext();) {
                Map.Entry<String, String> entry = iterator.next();
                String target = entry.getValue();
                String search = entry.getKey();
                Class<?> clazz = genericMap.get(search);
                if (clazz == null)
                    clazz = Object.class;
                if (genericMap.containsKey(target))
                    throw new RuntimeException(
                            "Impossible situation, it's a bug : {0} generic is both {1} and bound to {2}" + target
                                    + genericMap.get(target) + search);
                genericMap.put(target, clazz);
            }
        }
    }

    public Class<?> getBaseClass() {
        return this.myclass;
    }

    private void recurse(Class<?> clazz, Class<?> simplesup, ParameterizedType partype) {
        Type[] typeArguments = partype.getActualTypeArguments();
        TypeVariable<?>[] parameters = simplesup.getTypeParameters();

        for (int i = 0; i < typeArguments.length; i++) {
            if (typeArguments[i] instanceof Class) {
                genericMap.put(simplesup.getName() + "--" + parameters[i].getName(), (Class) typeArguments[i]);
            } else if (typeArguments[i] instanceof TypeVariable) {
                reverseIntermediate.put(clazz.getName() + "--" + ((TypeVariable<?>) typeArguments[i]).getName(),
                        simplesup.getName() + "--" + parameters[i].getName());
            }
        }

        recurse(simplesup);
    }

    private void recurse(Class<?> clazz) {
        Type supclass = clazz.getGenericSuperclass();
        Class simplesup = clazz.getSuperclass();
        if (supclass == null)
            return;
        if (supclass instanceof ParameterizedType) {
            recurse(clazz, simplesup, (ParameterizedType) supclass);
        } else {
            recurse(simplesup);
        }
    }

    /**
     * Return real, "generics dereferenced", parameter types for the given method.
     * 
     * @param method
     *          The method to analyze
     * @return The real classes, dereferencing generics
     */
    public GenericClass[] getParameterTypes(Method method) {
        Class<?> declaring = method.getDeclaringClass();
        String declname = declaring.getName();

        Type[] parameterTypes = method.getGenericParameterTypes();
        GenericClass[] ret = new GenericClass[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            Type type = parameterTypes[i];
            if (type instanceof Class) {
                ret[i] = forClass((Class<?>) type);
            } else if (type instanceof TypeVariable) {
                String name = ((TypeVariable<?>) type).getName();
                Class<?> sub = genericMap.get(declname + "--" + name);
                if (sub == null)
                    sub = Object.class;
                ret[i] = forClass(sub);
            } else {
                ret[i] = forGenericType(type);
            }
        }
        return ret;
    }

    public GenericClass resolveType(Type type) {
        if (type instanceof Class) {
            return forClass((Class<?>) type);
        } else if (type instanceof TypeVariable) {
            GenericDeclaration gd = ((TypeVariable<?>) type).getGenericDeclaration();
            Class<?> acclass = null;
            if (gd instanceof Class) {
                acclass = (Class<?>) gd;
            } else if (gd instanceof Method) {
                acclass = ((Method) gd).getDeclaringClass();
            } else if (gd instanceof Constructor) {
                acclass = ((Constructor<?>) gd).getDeclaringClass();
            }
            String name = ((TypeVariable<?>) type).getName();
            return forClass(genericMap.get(acclass.getName() + "--" + name));
        } else {
            return forGenericType(type);
        }
    }

    /**
     * Search for all occurrencies of a specific method.
     * <p>
     * The type parameters passed in may be Class or null. If they are null, that
     * means that we don't know which class they should be, if they are a class,
     * that means we are searching for that class, and the comparison is made on
     * dereferenced generics.
     * </p>
     * <p>
     * Specifying no parameter types explicitly means "a method without
     * parameters".
     * </p>
     * 
     * @param name
     *          The name of the method
     * @param parameterTypes
     *          The types of the parameters
     * @return A list of {@link MethodDef}, ordered with methods from current
     *         class first, then method from superclass and so on.
     */
    public List<MethodDef> findMethods(String name, Class<?>... parameterTypes) {
        List<MethodDef> founds = new ArrayList<MethodDef>();
        Class<?> current = myclass;
        while (current != null) {
            Method[] methods = current.getDeclaredMethods();
            for (Method method : methods) {
                if (!method.isBridge() && !method.isSynthetic() && method.getName().equals(name)) {
                    Type[] types = method.getGenericParameterTypes();
                    if (types.length == parameterTypes.length) {
                        GenericClass[] genericClasses = getParameterTypes(method);
                        Class<?>[] classes = toRawClasses(genericClasses);
                        boolean good = true;
                        for (int i = 0; i < types.length; i++) {
                            if (parameterTypes[i] != null) {
                                if (!classes[i].equals(parameterTypes[i])) {
                                    good = false;
                                    break;
                                }
                            }
                        }
                        if (good) {
                            MethodDef def = new MethodDef(method, genericClasses);
                            if (!founds.contains(def))
                                founds.add(def);
                        }
                    }
                }
            }
            current = current.getSuperclass();
        }
        return founds;
    }

    public static Class<?>[] toRawClasses(GenericClass[] genclasses) {
        Class<?>[] ret = new Class<?>[genclasses.length];
        for (int i = 0; i < genclasses.length; i++) {
            ret[i] = genclasses[i].getBaseClass();
        }
        return ret;
    }

    /**
     * Search for all methods having that name, no matter which parameter they
     * take.
     * 
     * @param name
     *          The name of the methods
     * @return A list of {@link MethodDef}, ordered with methods from current
     *         class first, then method from superclass and so on.
     */
    public List<MethodDef> findAllMethods(String name) {
        List<MethodDef> founds = new ArrayList<MethodDef>();
        Class<?> current = myclass;
        while (current != null) {
            Method[] methods = current.getDeclaredMethods();
            for (Method method : methods) {
                if (!method.isBridge() && !method.isSynthetic() && method.getName().equals(name)) {
                    MethodDef def = new MethodDef(method);
                    if (!founds.contains(def))
                        founds.add(def);
                }
            }
            current = current.getSuperclass();
        }
        return founds;
    }

    public List<MethodDef> getMethods() {
        List<MethodDef> founds = new ArrayList<MethodDef>();
        Class<?> current = myclass;
        while (current != null) {
            Method[] methods = current.getDeclaredMethods();
            for (Method method : methods) {
                if (!method.isBridge() && !method.isSynthetic()) {
                    MethodDef def = new MethodDef(method);
                    if (!founds.contains(def))
                        founds.add(def);
                }
            }
            current = current.getSuperclass();
        }
        return founds;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof GenericClass))
            return false;
        GenericClass oth = (GenericClass) obj;
        return this.getBaseClass().equals(oth.getBaseClass());
    }

    public class MethodDef {
        private Method method = null;

        private GenericClass[] params = null;

        MethodDef(Method m) {
            this.method = m;
        }

        MethodDef(Method m, GenericClass[] params) {
            this.method = m;
            this.params = params;
        }

        public String getName() {
            return this.method.getName();
        }

        public Method getBaseMethod() {
            return this.method;
        }

        public GenericClass[] getParameterTypes() {
            if (this.params == null) {
                this.params = GenericClass.this.getParameterTypes(method);
            }
            return this.params;
        }

        public GenericClass getReturnType() {
            return resolveType(method.getGenericReturnType());
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof MethodDef))
                return false;
            MethodDef oth = (MethodDef) other;
            return (method.getName().equals(oth.method.getName()))
                    && (Arrays.equals(getParameterTypes(), oth.getParameterTypes()));
        }

        public Class<?> getDeclaringClass() {
            return this.method.getDeclaringClass();
        }
    }

}