package org.apache.velocity.tools; /* * 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.io.InputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import org.apache.velocity.util.ArrayIterator; import org.apache.velocity.util.EnumerationIterator; /** * Repository for common class and reflection methods. * * @author Nathan Bubna * @version $Id: ClassUtils.java 511959 2007-02-26 19:24:39Z nbubna $ */ public class ClassUtils { private ClassUtils() {} // shortcuts for readability... private static final ClassLoader getThreadContextLoader() { return Thread.currentThread().getContextClassLoader(); } private static final ClassLoader getClassLoader() { return ClassUtils.class.getClassLoader(); } private static final ClassLoader getCallerLoader(Object caller) { if (caller instanceof Class) { return ((Class)caller).getClassLoader(); } else { return caller.getClass().getClassLoader(); } } /** * Load a class with a given name. * It will try to load the class in the following order: * <ul> * <li>From {@link Thread}.currentThread().getContextClassLoader() * <li>Using the basic {@link Class#forName(java.lang.String) } * <li>From {@link ClassUtils}.class.getClassLoader() * </ul> * * @param name Fully qualified class name to be loaded * @return Class object * @exception ClassNotFoundException if the class cannot be found */ public static Class getClass(String name) throws ClassNotFoundException { try { return getThreadContextLoader().loadClass(name); } catch (ClassNotFoundException e) { try { return Class.forName(name); } catch (ClassNotFoundException ex) { return getClassLoader().loadClass(name); } } } /** * Get an instance of a named class. * @param classname class name * @return new class instance * @throws ClassNotFoundException if class is not found * @throws IllegalAccessException if not granted * @throws InstantiationException if instance creation throwed */ public static Object getInstance(String classname) throws ClassNotFoundException, IllegalAccessException, InstantiationException { return getClass(classname).newInstance(); } /** * Load all resources with the specified name. If none are found, we * prepend the name with '/' and try again. * * This will attempt to load the resources from the following methods (in order): * <ul> * <li>Thread.currentThread().getContextClassLoader().getResources(name)</li> * <li>{@link ClassUtils}.class.getClassLoader().getResources(name)</li> * <li>{@link ClassUtils}.class.getResource(name)</li> * <li>{@link #getCallerLoader(Object caller)}.getResources(name)</li> * <li>caller.getClass().getResource(name)</li> * </ul> * * @param name The name of the resources to load * @param caller The instance or {@link Class} calling this method * @return the list of found resources */ public static List<URL> getResources(String name, Object caller) { Set<String> urls = new LinkedHashSet<String>(); // try to load all from the current thread context classloader addResources(name, urls, getThreadContextLoader()); // try to load all from this class' classloader if (!addResources(name, urls, getClassLoader())) { // ok, try to load one directly from this class addResource(name, urls, ClassUtils.class); } // try to load all from the classloader of the calling class if (!addResources(name, urls, getCallerLoader(caller))) { // try to load one directly from the calling class addResource(name, urls, caller.getClass()); } if (!urls.isEmpty()) { List<URL> result = new ArrayList<URL>(urls.size()); try { for (String url : urls) { result.add(new URL(url)); } } catch (MalformedURLException mue) { throw new IllegalStateException("A URL could not be recreated from its own toString() form", mue); } return result; } else if (!name.startsWith("/")) { // try again with a / in front of the name return getResources("/"+name, caller); } else { return Collections.emptyList(); } } private static final void addResource(String name, Set<String> urls, Class c) { URL url = c.getResource(name); if (url != null) { urls.add(url.toString()); } } private static final boolean addResources(String name, Set<String> urls, ClassLoader loader) { boolean foundSome = false; try { Enumeration<URL> e = loader.getResources(name); while (e.hasMoreElements()) { urls.add(e.nextElement().toString()); foundSome = true; } } catch (IOException ioe) { // ignore } return foundSome; } private static URL getResourceImpl(final String name, final Object caller) { URL url = getThreadContextLoader().getResource(name); if (url == null) { url = getClassLoader().getResource(name); if (url == null) { url = ClassUtils.class.getResource(name); if (url == null && caller != null) { Class callingClass = caller.getClass(); if (callingClass == Class.class) { callingClass = (Class)caller; } url = callingClass.getResource(name); } } } return url; } private static InputStream getResourceAsStreamImpl(final String name, final Object caller) { InputStream inputStream = getThreadContextLoader().getResourceAsStream(name); if (inputStream == null) { inputStream = getClassLoader().getResourceAsStream(name); if (inputStream == null) { inputStream = ClassUtils.class.getResourceAsStream(name); if (inputStream == null && caller != null) { Class callingClass = caller.getClass(); if (callingClass == Class.class) { callingClass = (Class)caller; } inputStream = callingClass.getResourceAsStream(name); } } } return inputStream; } /** * Load a given resource. * This method will try to load the resource using the following methods (in order): * <ul> * <li>Thread.currentThread().getContextClassLoader().getResource(name)</li> * <li>{@link ClassUtils}.class.getClassLoader().getResource(name)</li> * <li>{@link ClassUtils}.class.getResource(name)</li> * <li>caller.getClass().getResource(name) or, if caller is a Class, * caller.getResource(name)</li> * </ul> * * @param name The name of the resource to load * @param caller The instance or {@link Class} calling this method * @return the found URL, or null if not found */ public static URL getResource(final String name, final Object caller) { URL url = null; if (System.getSecurityManager() != null) { url = AccessController.doPrivileged( new PrivilegedAction<URL>() { @Override public URL run() { return getResourceImpl(name, caller); } }); } else { url = getResourceImpl(name, caller); } return url; } /** * This is a convenience method to load a resource as a stream. * The algorithm used to find the resource is given in getResource() * * @param name The name of the resource to load * @param caller The instance or {@link Class} calling this method * @return the resource input stream or null if not found */ public static InputStream getResourceAsStream(final String name, final Object caller) { InputStream inputStream = null; if (System.getSecurityManager() != null) { inputStream = AccessController.doPrivileged( new PrivilegedAction<InputStream>() { @Override public InputStream run() { return getResourceAsStreamImpl(name, caller); } }); } else { inputStream = getResourceAsStreamImpl(name, caller); } return inputStream; } /** * Find a callable method in a class * @param clazz target class * @param name method name * @param params method arguments classes * @return method object * @throws SecurityException if not granted */ public static Method findMethod(Class clazz, String name, Class... params) throws SecurityException { try { // check for a public setup(Map) method first return clazz.getMethod(name, params); } catch (NoSuchMethodException nsme) { // ignore this } return findDeclaredMethod(clazz, name, params); } /** * Find a declared method in a class. It will be made accessible if needed and allowed. * @param clazz target class * @param name method name * @param params method arguments classes * @return * @throws SecurityException if not allowed */ public static Method findDeclaredMethod(Class clazz, String name, Class... params) throws SecurityException { try { // check for a protected one Method method = clazz.getDeclaredMethod(name, params); if (method != null) { // and give this class access to it method.setAccessible(true); return method; } } catch (NoSuchMethodException nsme) { // ignore this } // ok, didn't find it declared in this class, try the superclass Class supclazz = clazz.getSuperclass(); if (supclazz != null) { // recurse upward return findDeclaredMethod(supclazz, name, params); } // otherwise, return null return null; } /** * Given a static field path, aka <i>classname</i>.<i>field</i>, get the field value. * @param fieldPath field path * @return field value * @throws ClassNotFoundException if class hasn't been found * @throws NoSuchFieldException if field hasn't been found * @throws SecurityException if not granted * @throws IllegalAccessException if field is not accessible */ public static Object getFieldValue(String fieldPath) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalAccessException { int lastDot = fieldPath.lastIndexOf('.'); String classname = fieldPath.substring(0, lastDot); String fieldname = fieldPath.substring(lastDot + 1, fieldPath.length()); Class clazz = getClass(classname); return getFieldValue(clazz, fieldname); } /** * Given a class and a static field name, get the field value. * @param clazz target class * @param fieldname field name * @return field value * @throws NoSuchFieldException if field hasn't been found * @throws SecurityException if not granted * @throws IllegalAccessException if field is not accessible */ public static Object getFieldValue(Class clazz, String fieldname) throws NoSuchFieldException, SecurityException, IllegalAccessException { Field field = clazz.getField(fieldname); int mod = field.getModifiers(); if (!Modifier.isStatic(mod)) { throw new UnsupportedOperationException("Field "+fieldname+" in class "+clazz.getName()+" is not static. Only static fields are supported."); } return field.get(null); } /** * Retrieves an Iterator from or creates and Iterator for the specified object. * This method is almost entirely copied from Engine's UberspectImpl class. * @param obj the target obj * @return an iterator over the content of obj, or null if not found * @throws NoSuchMethodException if no iterator() method * @throws IllegalAccessException if iterator() method not callable * @throws InvocationTargetException if iterator() method throwed */ public static Iterator getIterator(Object obj) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (obj.getClass().isArray()) { return new ArrayIterator(obj); } else if (obj instanceof Collection) { return ((Collection) obj).iterator(); } else if (obj instanceof Map) { return ((Map) obj).values().iterator(); } else if (obj instanceof Iterator) { return ((Iterator) obj); } else if (obj instanceof Iterable) { return ((Iterable)obj).iterator(); } else if (obj instanceof Enumeration) { return new EnumerationIterator((Enumeration) obj); } else { // look for an iterator() method to support // any user tools/DTOs that want to work in // foreach w/o implementing the Collection interface Method iter = obj.getClass().getMethod("iterator"); if (Iterator.class.isAssignableFrom(iter.getReturnType())) { return (Iterator)iter.invoke(obj); } else { return null; } } } private static String factoryMethodPrefixes[] = { "create", "new", "get" }; /** * <p>Given a factory class and a target class, search for the following methods:</p> * <ul> * <li><code>create<i>TargetClassname</i>()</code>,</li> * <li><code>new<i>TargetClassname</i>()</code>, or</li> * <li><code>get<i>TargetClassname</i>()</code>.</li> * </ul> * @param factory factory class * @param target target class * @return first factory method found, or null otherwise */ public static Method findFactoryMethod(Class factory, Class target) { Method ret = null; String undecoratedName = target.getSimpleName(); for (String prefix : factoryMethodPrefixes) { String methodName = prefix + undecoratedName; ret = findMethod(factory, methodName, new Class[] {}); if (ret != null) break; } return ret; } public static Method findGetter(String getterName, Class clazz) throws NoSuchMethodException { return findGetter(getterName, clazz, true); } public static Method findGetter(String getterName, Class clazz, boolean mandatory) throws NoSuchMethodException { do { for (Method method : clazz.getDeclaredMethods()) { // prefix matching: we allow a method name like setWriteAccess for a parameter like write="..." if (method.getParameterCount() == 0 && method.getName().startsWith(getterName)) { return method; } } clazz = clazz.getSuperclass(); } while (clazz != Object.class); if (mandatory) { throw new NoSuchMethodException(clazz.getName() + "::" + getterName); } else { return null; } } public static Method findSetter(String setterName, Class clazz) throws NoSuchMethodException { return findSetter(setterName, clazz, x -> true); } public static Method findSetter(String setterName, Class clazz, Predicate<Class> argumentClassFilter) throws NoSuchMethodException { return findSetter(setterName, clazz, argumentClassFilter, true); } public static Method findSetter(String setterName, Class clazz, Predicate<Class> argumentClassFilter, boolean mandatory) throws NoSuchMethodException { do { for (Method method : clazz.getDeclaredMethods()) { // prefix matching: we allow a method name like setWriteAccess for a parameter like write="..." if (method.getParameterCount() == 1 && method.getName().startsWith(setterName) && argumentClassFilter.test(method.getParameterTypes()[0])) { return method; } } clazz = clazz.getSuperclass(); } while (clazz != Object.class); if (mandatory) { throw new NoSuchMethodException(clazz.getName() + "::" + setterName); } else { return null; } } }