org.apache.velocity.util.introspection.ClassMap Maven / Gradle / Ivy
The newest version!
package org.apache.velocity.util.introspection;
/*
* 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 org.slf4j.Logger;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A cache of introspection information for a specific class instance.
* Keys {@link java.lang.reflect.Method} objects by a concatenation of the
* method name and the names of classes that make up the parameters.
*
* @author Jason van Zyl
* @author Bob McWhirter
* @author Attila Szegedi
* @author Geir Magnusson Jr.
* @author Henning P. Schmiedehausen
* @author Nathan Bubna
* @author Claude Brisson
* @version $Id$
*/
public class ClassMap
{
/** Set true if you want to debug the reflection code */
private static final boolean debugReflection = false;
/** Class logger */
private final Logger log;
/**
* Class passed into the constructor used to as
* the basis for the Method map.
*/
private final Class clazz;
private final MethodCache methodCache;
/**
* Standard constructor
* @param clazz The class for which this ClassMap gets constructed.
* @param log logger
*/
public ClassMap(final Class clazz, final Logger log)
{
this(clazz, log, null);
}
/**
* Standard constructor
* @param clazz The class for which this ClassMap gets constructed.
* @param log logger
* @param conversionHandler conversion handler
* @since 2.0
*/
public ClassMap(final Class clazz, final Logger log, final TypeConversionHandler conversionHandler)
{
this.clazz = clazz;
this.log = log;
if (debugReflection)
{
log.debug("=================================================================");
log.debug("== Class: {}", clazz);
}
methodCache = createMethodCache(conversionHandler);
if (debugReflection)
{
log.debug("=================================================================");
}
}
/**
* Returns the class object whose methods are cached by this map.
*
* @return The class object whose methods are cached by this map.
*/
public Class getCachedClass()
{
return clazz;
}
/**
* Find a Method using the method name and parameter objects.
*
* @param name The method name to look up.
* @param params An array of parameters for the method.
* @return A Method object representing the method to invoke or null.
* @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
*/
public Method findMethod(final String name, final Object[] params)
throws MethodMap.AmbiguousException
{
return methodCache.get(name, params);
}
/**
* Populate the Map of direct hits. These
* are taken from all the public methods
* that our class, its parents and their implemented interfaces provide.
*/
private MethodCache createMethodCache(TypeConversionHandler conversionHandler)
{
MethodCache methodCache = new MethodCache(log, conversionHandler);
//
// Looks through all elements in the class hierarchy. This one is bottom-first (i.e. we start
// with the actual declaring class and its interfaces and then move up (superclass etc.) until we
// hit java.lang.Object. That is important because it will give us the methods of the declaring class
// which might in turn be abstract further up the tree.
//
// We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
// hit with Tomcat 5.5).
//
// We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
// until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
// hit the public elements sooner or later because we reflect all the public elements anyway.
//
// Ah, the miracles of Java for(;;) ...
for (Class classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
{
if (Modifier.isPublic(classToReflect.getModifiers()))
{
populateMethodCacheWith(methodCache, classToReflect);
}
Class [] interfaces = classToReflect.getInterfaces();
for (Class anInterface : interfaces)
{
populateMethodCacheWithInterface(methodCache, anInterface);
}
}
// return the already initialized cache
return methodCache;
}
/* recurses up interface heirarchy to get all super interfaces (VELOCITY-689) */
private void populateMethodCacheWithInterface(MethodCache methodCache, Class iface)
{
if (Modifier.isPublic(iface.getModifiers()))
{
populateMethodCacheWith(methodCache, iface);
}
Class[] supers = iface.getInterfaces();
for (Class aSuper : supers)
{
populateMethodCacheWithInterface(methodCache, aSuper);
}
}
private void populateMethodCacheWith(MethodCache methodCache, Class classToReflect)
{
if (debugReflection)
{
log.debug("Reflecting {}", classToReflect);
}
try
{
Method[] methods = classToReflect.getDeclaredMethods();
for (Method method : methods)
{
int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers))
{
methodCache.put(method);
}
}
}
catch (SecurityException se) // Everybody feels better with...
{
log.debug("While accessing methods of {}:", classToReflect, se);
}
}
/**
* This is the cache to store and look up the method information.
*
* @author Henning P. Schmiedehausen
* @version $Id$
*/
private static final class MethodCache
{
private static final Object CACHE_MISS = new Object();
private static final String NULL_ARG = Object.class.getName();
private static final Map, String> convertPrimitives = new HashMap();
static
{
convertPrimitives.put(Boolean.TYPE, Boolean.class.getName());
convertPrimitives.put(Byte.TYPE, Byte.class.getName());
convertPrimitives.put(Character.TYPE, Character.class.getName());
convertPrimitives.put(Double.TYPE, Double.class.getName());
convertPrimitives.put(Float.TYPE, Float.class.getName());
convertPrimitives.put(Integer.TYPE, Integer.class.getName());
convertPrimitives.put(Long.TYPE, Long.class.getName());
convertPrimitives.put(Short.TYPE, Short.class.getName());
}
/** Class logger */
private final Logger log;
/**
* Cache of Methods, or CACHE_MISS, keyed by method
* name and actual arguments used to find it.
*/
private final Map