Java tutorial
/* * 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(); } } }