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

com.sun.faces.util.ReflectionUtils Maven / Gradle / Ivy

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.2
Show newest version
/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.faces.util;

import static java.beans.Introspector.getBeanInfo;
import static java.beans.PropertyEditorManager.findEditor;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 

* A set of utility methods to make working with Classes and Reflection a little easier. *

*/ public final class ReflectionUtils { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); /** *

* Cache *

*/ private static final Map> REFLECTION_CACHE = new WeakHashMap<>(); // ------------------------------------------------------------ Constructors private ReflectionUtils() { } // ---------------------------------------------------------- Public Methods /** * Sets a collection of properties of a given object to the values associated with those properties. *

* In the map that represents these properties, each key represents the name of the property, with the value associated * with that key being the value that is set for the property. *

* E.g. map entry key = foo, value = "bar", which "bar" an instance of String, will conceptually result in the following * call: object.setFoo("string"); * *

* NOTE: This particular method assumes that there's a write method for each property in the map with the right type. No * specific checking is done whether this is indeed the case. * * @param object the object on which properties will be set * @param propertiesToSet the map containing properties and their values to be set on the object */ public static void setProperties(Object object, Map propertiesToSet) { try { Map availableProperties = new HashMap<>(); for (PropertyDescriptor propertyDescriptor : getBeanInfo(object.getClass()).getPropertyDescriptors()) { availableProperties.put(propertyDescriptor.getName(), propertyDescriptor); } for (Map.Entry propertyToSet : propertiesToSet.entrySet()) { availableProperties.get(propertyToSet.getKey()).getWriteMethod().invoke(object, propertyToSet.getValue()); } } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new IllegalStateException(e); } } /** * Sets a collection of properties of a given object to the (optionally coerced) values associated with those * properties. *

* In the map that represents these properties, each key represents the name of the property, with the value associated * with that key being the value that is set for the property. *

* E.g. map entry key = foo, value = "bar", which "bar" an instance of String, will conceptually result in the following * call: object.setFoo("string"); * *

* NOTE 1: In case the value is a String, and the target type is not String, the standard property editor mechanism will * be used to attempt a conversion. * *

* Note 2: This method operates somewhat as the reverse of Reflection#setProperties(Object, Map) Here only the * available writable properties of the object are matched against the map with properties to set. Properties in the map * for which there isn't a corresponding writable property on the object are ignored. * *

* Following the above two notes, use this method when attempting to set properties on an object in a lenient best * effort basis. Use Reflection#setProperties(Object, Map) when all properties need to be set with the exact * type as the value appears in the map. * * * @param object the object on which properties will be set * @param propertiesToSet the map containing properties and their values to be set on the object */ public static void setPropertiesWithCoercion(Object object, Map propertiesToSet) { try { for (PropertyDescriptor property : getBeanInfo(object.getClass()).getPropertyDescriptors()) { Method setter = property.getWriteMethod(); if (setter == null) { continue; } if (propertiesToSet.containsKey(property.getName())) { Object value = propertiesToSet.get(property.getName()); if (value instanceof String && !property.getPropertyType().equals(String.class)) { // Try to convert Strings to the type expected by the converter PropertyEditor editor = findEditor(property.getPropertyType()); editor.setAsText((String) value); value = editor.getValue(); } property.getWriteMethod().invoke(object, value); } } } catch (Exception e) { // NOPMD throw new IllegalStateException(e); } } /** * Finds a method based on the method name, amount of parameters and limited typing, if necessary prefixed with "get". *

* Note that this supports overloading, but a limited one. Given an actual parameter of type Long, this will select a * method accepting Number when the choice is between Number and a non-compatible type like String. However, it will NOT * select the best match if the choice is between Number and Long. * * @param base the object in which the method is to be found * @param methodName name of the method to be found * @param params the method parameters * @return a method if one is found, null otherwise */ public static Method findMethod(Object base, String methodName, Object[] params) { List methods = new ArrayList<>(); for (Method method : base.getClass().getMethods()) { if (method.getName().equals(methodName) && method.getParameterTypes().length == params.length) { methods.add(method); } } if (methods.size() == 1) { return methods.get(0); } if (methods.size() > 1) { // Overloaded methods were found. Try to get a match for (Method method : methods) { boolean match = true; Class[] candidateParams = method.getParameterTypes(); for (int i = 0; i < params.length; i++) { if (!candidateParams[i].isInstance(params[i])) { match = false; break; } } // If all candidate parameters were expected and for none of them the actual // parameter was NOT an instance, we have a match if (match) { return method; } // Else, at least one parameter was not an instance // Go ahead a test then next methods } } return null; } /** * Returns the Class instance associated with the class of the given string, using the context class loader and if that * fails the defining class loader of the current class. * * @param className fully qualified class name of the class for which a Class instance needs to be created * @return the Class object for the class with the given name. * @throws IllegalStateException if the class cannot be found. */ public static Class toClass(String className) { try { return Class.forName(className, true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { try { return Class.forName(className); } catch (Exception ignore) { ignore = null; // Just continue to IllegalStateException on original ClassNotFoundException. } throw new IllegalStateException(e); } } /** * Creates an instance of a class with the given fully qualified class name. * * @param The generic object type. * @param className fully qualified class name of the class for which an instance needs to be created * @return an instance of the class denoted by className * @throws IllegalStateException if the class cannot be found */ @SuppressWarnings("unchecked") public static T instance(String className) { return (T) instance(toClass(className)); } /** * Creates a new instance of the class represented by the given Class object * * @param The generic object type. * @param clazz the Class object for which an instance needs to be created * @return an instance of the class as given by the clazz parameter * @throws IllegalStateException if the class cannot be found, or cannot be instantiated or when a security manager * prevents this operation */ public static T instance(Class clazz) { try { return clazz.getDeclaredConstructor().newInstance(); } catch (IllegalArgumentException | ReflectiveOperationException | SecurityException e) { throw new IllegalStateException(e); } } /** *

* Clears the cache for the specified ClassLoader. *

*

* This method MUST be called when ConfigureListener * .contextDestroyed() is called. *

* * @param loader the ClassLoader whose associated cache should be cleared */ public static synchronized void clearCache(ClassLoader loader) { REFLECTION_CACHE.remove(loader); } public static synchronized void initCache(ClassLoader loader) { if (REFLECTION_CACHE.get(loader) == null) { REFLECTION_CACHE.put(loader, new ConcurrentHashMap<>()); } } /** *

* Returns the Constructor appropriate to the specified Class and parameters. *

* * @param clazz the Class of interest * @param params the parameters for the constructor of the provided Class * @return a Constructor that can be invoked with the specified parameters */ public static Constructor lookupConstructor(Class clazz, Class... params) { ClassLoader loader = Util.getCurrentLoader(clazz); if (loader == null) { return null; } return getMetaData(loader, clazz).lookupConstructor(params); } /** *

* Returns the Method appropriate to the specified object instance, method name, and parameters. *

* * @param object the Object instance of interest * @param methodName the name of the method * @param params the parameters for the specified method * @return a Method that can be invoked with the specified parameters */ public static Method lookupMethod(Object object, String methodName, Class... params) { Class clazz = object.getClass(); ClassLoader loader = Util.getCurrentLoader(clazz); if (loader == null) { return null; } return getMetaData(loader, clazz).lookupMethod(methodName, params); } /** *

* Returns the Method appropriate to the specified Class, method name, and parameters. *

* * @param clazz the Class of interest * @param methodName the name of the method * @param params the parameters for the specified method * @return a Method that can be invoked with the specified parameters */ public static Method lookupMethod(Class clazz, String methodName, Class... params) { ClassLoader loader = Util.getCurrentLoader(clazz); if (loader == null) { return null; } return getMetaData(loader, clazz).lookupMethod(methodName, params); } /** *

* Constructs a new object instance based off the provided class name. *

* * @param className the class of the object to instantiate * @return a new instances of said class * @throws InstantiationException if the class cannot be instantiated * @throws IllegalAccessException if there is a security violation */ public static Object newInstance(String className) throws IllegalArgumentException, ReflectiveOperationException, SecurityException { ClassLoader loader = Util.getCurrentLoader(null); if (loader == null) { return null; } return getMetaData(loader, className).lookupClass().getDeclaredConstructor().newInstance(); } /** *

* Obtain a Class instance based on the provided String name. *

* * @param className the class to look up * @return the Class corresponding to className */ public static Class lookupClass(String className) { ClassLoader loader = Util.getCurrentLoader(null); if (loader == null) { return null; } return getMetaData(loader, className).lookupClass(); } /** * @param className the fully qualified class name * @param propertyName a JavaBeans property name * @return a method suitable for setting a JavaBeans property, or null if the property doesn't exist or is * readonly. */ public static Method lookupWriteMethod(String className, String propertyName) { ClassLoader loader = Util.getCurrentLoader(null); if (loader == null) { return null; } return getMetaData(loader, className).lookupWriteMethod(propertyName); } /** * @param className the fully qualified class name * @param propertyName a JavaBeans property name * @return a method suitable for obtaining the value of a JavaBeans property, or null if the property * doesn't exist or can't be read. */ public static Method lookupReadMethod(String className, String propertyName) { ClassLoader loader = Util.getCurrentLoader(null); if (loader == null) { return null; } return getMetaData(loader, className).lookupReadMethod(propertyName); } // --------------------------------------------------------- Private Methods /** *

* Return the MetaData for the specified Class. *

* *

* This will check the cache associated with the specified ClassLoader. If there is no cache hit, then a * new MetaData instance will be created and stored. * * @param loader ClassLoader * @param clazz the Class of interest * @return a MetaData object for the specified Class */ private static MetaData getMetaData(ClassLoader loader, Class clazz) { ConcurrentMap cache = REFLECTION_CACHE.get(loader); if (cache == null) { initCache(loader); cache = REFLECTION_CACHE.get(loader); } MetaData meta = cache.get(clazz.getName()); if (meta == null) { meta = new MetaData(clazz); cache.put(clazz.getName(), meta); } return meta; } /** *

* Return the MetaData for the specified className. *

* *

* This will check the cache associated with the specified ClassLoader. If there is no cache hit, then a * new MetaData instance will be created and stored. * * @param loader ClassLoader * @param className the class of interest * @return a MetaData object for the specified Class */ private static MetaData getMetaData(ClassLoader loader, String className) { ConcurrentMap cache = REFLECTION_CACHE.get(loader); if (cache == null) { initCache(loader); cache = REFLECTION_CACHE.get(loader); } MetaData meta = cache.get(className); if (meta == null) { try { Class clazz = Util.loadClass(className, cache); meta = new MetaData(clazz); cache.put(className, meta); } catch (ClassNotFoundException cnfe) { return null; } } return meta; } /** *

* MetaData contains lookup methods for Constructors and Methods of a particular Class. */ private static final class MetaData { Map constructors; Map> methods; Map> declaredMethods; Map propertyDescriptors; Class clazz; // ------------------------------------------------------------ Constructors /** *

* Constructs a new MetaData instance for the specified class. *

* * @param clazz class to construct a new MetaData instance from. */ public MetaData(Class clazz) { String name; this.clazz = clazz; Constructor[] ctors = clazz.getConstructors(); constructors = new HashMap<>(ctors.length, 1.0f); for (Constructor ctor : ctors) { constructors.put(getKey(ctor.getParameterTypes()), ctor); } Method[] meths = clazz.getMethods(); methods = new HashMap<>(meths.length, 1.0f); for (Method method : meths) { name = method.getName(); methods.computeIfAbsent(name, k -> new HashMap<>(4, 1.0f)) .put(getKey(method.getParameterTypes()), method); } meths = clazz.getDeclaredMethods(); declaredMethods = new HashMap<>(meths.length, 1.0f); for (Method meth : meths) { name = meth.getName(); declaredMethods.computeIfAbsent(name, k -> new HashMap<>(4, 1.0f)) .put(getKey(meth.getParameterTypes()), meth); } try { BeanInfo info = Introspector.getBeanInfo(clazz); PropertyDescriptor[] pds = info.getPropertyDescriptors(); if (pds != null) { if (propertyDescriptors == null) { propertyDescriptors = new HashMap<>(pds.length, 1.0f); } for (PropertyDescriptor pd : pds) { propertyDescriptors.put(pd.getName(), pd); } } } catch (IntrospectionException ie) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, ie.toString(), ie); } } } // ---------------------------------------------------------- Public Methods /** *

* Looks up a Constructor based off the specified params. *

* * @param params constructor parameters * @return the Constructor appropriate to the specified parameters or null */ public Constructor lookupConstructor(Class... params) { return constructors.get(getKey(params)); } /** *

* Looks up a Method based off the specified method name and params. *

* * @param name the name of the Method
* @param params the Method parameters * @return the Method appropriate to the specified name and parameters or null */ public Method lookupMethod(String name, Class... params) { Map map = methods.get(name); Integer key = getKey(params); Method result = null; if (null == map || null == (result = map.get(key))) { map = declaredMethods.get(name); if (null != map) { result = map.get(key); } } return result; } /** *

* Looks up the class for this MetaData instance. *

* * @return the Class for this MetaData instance */ public Class lookupClass() { return clazz; } /** * @param propName a JavaBeans property name * @return a method suitable for setting a JavaBeans property, or null if the property doesn't exist or is * readonly. */ public Method lookupWriteMethod(String propName) { if (propertyDescriptors == null) { return null; } PropertyDescriptor pd = propertyDescriptors.get(propName); if (pd != null) { return pd.getWriteMethod(); } return null; } /** * @param propName a JavaBeans property name * @return a method suitable for obtaining the value of a JavaBeans property, or null if the property * doesn't exist or can't be read. */ public Method lookupReadMethod(String propName) { if (propertyDescriptors == null) { return null; } PropertyDescriptor pd = propertyDescriptors.get(propName); if (pd != null) { return pd.getReadMethod(); } return null; } // --------------------------------------------------------- Private Methods /** * Return a hashcode of all the class parameters. * * @param params the parameters to a Constructor or a Method instance * @return the result of Arrays.deepHashCode */ private static Integer getKey(Class... params) { return Arrays.deepHashCode(params); } } } // END ReflectionUtils




© 2015 - 2025 Weber Informatics LLC | Privacy Policy