All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.jexl3.internal.introspection.ClassMap Maven / Gradle / Ivy

Go to download

The Apache Commons JEXL library is an implementation of the JSTL Expression Language with extensions.

There is a newer version: 3.4.0
Show newest version
/*
 * 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.
 */
package org.apache.commons.jexl3.internal.introspection;

import org.apache.commons.logging.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A cache of introspection information for a specific class instance.
 * Keys objects by an aggregation of the method name and the classes
 * that make up the parameters.
 * 

* Originally taken from the Velocity tree so we can be self-sufficient. *

* @see MethodKey * @since 1.0 */ final class ClassMap { /** * A method that returns itself used as a marker for cache miss, * allows the underlying cache map to be strongly typed. * @return itself as a method */ public static Method cacheMiss() { try { return ClassMap.class.getMethod("cacheMiss"); } catch (Exception xio) { // this really cant make an error... return null; } } /** The cache miss marker method. */ private static final Method CACHE_MISS = cacheMiss(); /** * This is the cache to store and look up the method information. *

* It stores the association between: * - a key made of a method name and an array of argument types. * - a method. *

*

* Since the invocation of the associated method is dynamic, there is no need (nor way) to differentiate between * foo(int,int) and foo(Integer,Integer) since in practise, only the latter form will be used through a call. * This of course, applies to all 8 primitive types. *

* Uses ConcurrentMap since 3.0, marginally faster than 2.1 under contention. */ private final ConcurrentMap byKey = new ConcurrentHashMap(); /** * Keep track of all methods with the same name; this is not modified after creation. */ private final Map byName = new HashMap(); /** * Cache of fields. */ private final Map fieldCache; /** * Standard constructor. * * @param aClass the class to deconstruct. * @param log the logger. */ @SuppressWarnings("LeakingThisInConstructor") ClassMap(Class aClass, Log log) { // eagerly cache methods create(this, aClass, log); // eagerly cache public fields Field[] fields = aClass.getFields(); if (fields.length > 0) { Map cache = new HashMap(); for (Field field : fields) { if (Modifier.isPublic(field.getModifiers()) && Permissions.allow(field)) { cache.put(field.getName(), field); } } fieldCache = cache; } else { fieldCache = Collections.emptyMap(); } } /** * Find a Field using its name. * @param fname the field name * @return A Field object representing the field to invoke or null. */ Field getField(final String fname) { return fieldCache.get(fname); } /** * Gets the field names cached by this map. * @return the array of field names */ String[] getFieldNames() { return fieldCache.keySet().toArray(new String[fieldCache.size()]); } /** * Gets the methods names cached by this map. * @return the array of method names */ String[] getMethodNames() { return byName.keySet().toArray(new String[byName.size()]); } /** * Gets all the methods with a given name from this map. * @param methodName the seeked methods name * @return the array of methods (null or non-empty) */ Method[] getMethods(final String methodName) { Method[] lm = byName.get(methodName); if (lm != null && lm.length > 0) { return lm.clone(); } else { return null; } } /** * Find a Method using the method name and parameter objects. *

* Look in the methodMap for an entry. If found, * it'll either be a CACHE_MISS, in which case we * simply give up, or it'll be a Method, in which * case, we return it. *

*

* If nothing is found, then we must actually go * and introspect the method from the MethodMap. *

* @param methodKey the method key * @return A Method object representing the method to invoke or null. * @throws MethodKey.AmbiguousException When more than one method is a match for the parameters. */ Method getMethod(final MethodKey methodKey) throws MethodKey.AmbiguousException { // Look up by key Method cacheEntry = byKey.get(methodKey); // We looked this up before and failed. if (cacheEntry == CACHE_MISS) { return null; } else if (cacheEntry == null) { try { // That one is expensive... Method[] methodList = byName.get(methodKey.getMethod()); if (methodList != null) { cacheEntry = methodKey.getMostSpecificMethod(methodList); } if (cacheEntry == null) { byKey.put(methodKey, CACHE_MISS); } else { byKey.put(methodKey, cacheEntry); } } catch (MethodKey.AmbiguousException ae) { // that's a miss :-) byKey.put(methodKey, CACHE_MISS); throw ae; } } // Yes, this might just be null. return cacheEntry; } /** * Populate the Map of direct hits. These are taken from all the public methods * that our class, its parents and their implemented interfaces provide. * @param cache the ClassMap instance we create * @param classToReflect the class to cache * @param log the Log */ private static void create(ClassMap cache, Class classToReflect, Log log) { // // Build a list of 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. // for (; classToReflect != null; classToReflect = classToReflect.getSuperclass()) { if (Modifier.isPublic(classToReflect.getModifiers())) { populateWithClass(cache, classToReflect, log); } Class[] interfaces = classToReflect.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { populateWithInterface(cache, interfaces[i], log); } } // now that we've got all methods keyed in, lets organize them by name if (!cache.byKey.isEmpty()) { List lm = new ArrayList(cache.byKey.size()); for (Method method : cache.byKey.values()) { lm.add(method); } // sort all methods by name Collections.sort(lm, new Comparator() { @Override public int compare(Method o1, Method o2) { return o1.getName().compareTo(o2.getName()); } }); // put all lists of methods with same name in byName cache int start = 0; while (start < lm.size()) { String name = lm.get(start).getName(); int end = start + 1; while (end < lm.size()) { String walk = lm.get(end).getName(); if (walk.equals(name)) { end += 1; } else { break; } } Method[] lmn = lm.subList(start, end).toArray(new Method[end - start]); cache.byName.put(name, lmn); start = end; } } } /** * Recurses up interface hierarchy to get all super interfaces. * @param cache the cache to fill * @param iface the interface to populate the cache from * @param log the Log */ private static void populateWithInterface(ClassMap cache, Class iface, Log log) { if (Modifier.isPublic(iface.getModifiers())) { populateWithClass(cache, iface, log); } Class[] supers = iface.getInterfaces(); for (int i = 0; i < supers.length; i++) { populateWithInterface(cache, supers[i], log); } } /** * Recurses up class hierarchy to get all super classes. * @param cache the cache to fill * @param clazz the class to populate the cache from * @param log the Log */ private static void populateWithClass(ClassMap cache, Class clazz, Log log) { try { Method[] methods = clazz.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { Method mi = methods[i]; if (Modifier.isPublic(mi.getModifiers()) && Permissions.allow(mi)) { // add method to byKey cache; do not override cache.byKey.putIfAbsent(new MethodKey(mi), mi); } } } catch (SecurityException se) { // Everybody feels better with... if (log.isDebugEnabled()) { log.debug("While accessing methods of " + clazz + ": ", se); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy