package org.apache.velocity.util; /* * 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.apache.velocity.context.InternalContextAdapter; import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.VelocityException; import org.apache.velocity.runtime.parser.node.ASTMethod.MethodCacheKey; import org.apache.velocity.runtime.parser.node.SimpleNode; import org.apache.velocity.util.introspection.Info; import org.apache.velocity.util.introspection.IntrospectionCacheData; import org.apache.velocity.util.introspection.VelMethod; import java.io.InputStream; /** * Simple utility functions for manipulating classes and resources * from the classloader. * * @author Will Glass-Husain * @version $Id$ * @since 1.5 */ public class ClassUtils { /** * Utility class; cannot be instantiated. */ private ClassUtils() { } /** * Return the specified class. Checks the ThreadContext classloader first, * then uses the System classloader. Should replace all calls to * Class.forName( claz ) (which only calls the System class * loader) when the class might be in a different classloader (e.g. in a * webapp). * * @param clazz the name of the class to instantiate * @return the requested Class object * @throws ClassNotFoundException */ public static Class getClass(String clazz) throws ClassNotFoundException { /** * Use the Thread context classloader if possible */ ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader != null) { try { return Class.forName(clazz, true, loader); } catch (ClassNotFoundException E) { /* * If not found with ThreadContext loader, fall thru to * try System classloader below (works around bug in ant). */ } } /* * Thread context classloader isn't working out, so use system loader. */ return Class.forName(clazz); } /** * Return a new instance of the given class. Checks the ThreadContext * classloader first, then uses the System classloader. Should replace all * calls to Class.forName( claz ).newInstance() (which only * calls the System class loader) when the class might be in a different * classloader (e.g. in a webapp). * * @param clazz the name of the class to instantiate * @return an instance of the specified class * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static Object getNewInstance(String clazz) throws ClassNotFoundException,IllegalAccessException,InstantiationException { return getClass(clazz).newInstance(); } /** * Finds a resource with the given name. Checks the Thread Context * classloader, then uses the System classloader. Should replace all * calls to Class.getResourceAsString when the resource * might come from a different classloader. (e.g. a webapp). * @param claz Class to use when getting the System classloader (used if no Thread * Context classloader available or fails to get resource). * @param name name of the resource * @return InputStream for the resource. */ public static InputStream getResourceAsStream(Class claz, String name) { InputStream result = null; /* * remove leading slash so path will work with classes in a JAR file */ while (name.startsWith("/")) { name = name.substring(1); } ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); if (classLoader == null) { classLoader = claz.getClassLoader(); result = classLoader.getResourceAsStream( name ); } else { result= classLoader.getResourceAsStream( name ); /* * for compatibility with texen / ant tasks, fall back to * old method when resource is not found. */ if (result == null) { classLoader = claz.getClassLoader(); if (classLoader != null) result = classLoader.getResourceAsStream( name ); } } return result; } /** * Lookup a VelMethod object given the method signature that is specified in * the passed in parameters. This method first searches the cache, if not found in * the cache then uses reflections to inspect Object o, for the given method. * @param methodName Name of method * @param params Array of objects that are parameters to the method * @param paramClasses Array of Classes corresponding to the types in params. * @param o Object to introspect for the given method. * @param context Context from which the method cache is acquired * @param node ASTNode, used for error reporting. * @param strictRef If no method is found, throw an exception, never return null in this case * @return VelMethod object if the object is found, null if not matching method is found */ public static VelMethod getMethod(String methodName, Object[] params, Class[] paramClasses, Object o, InternalContextAdapter context, SimpleNode node, boolean strictRef) { VelMethod method = null; try { /* * check the cache */ boolean classObject = (o instanceof Class); MethodCacheKey mck = new MethodCacheKey(methodName, paramClasses, classObject); IntrospectionCacheData icd = context.icacheGet(mck); Class clazz = classObject ? (Class)o : o.getClass(); /* * like ASTIdentifier, if we have cache information, and the Class of * Object o is the same as that in the cache, we are safe. */ if (icd != null && icd.contextData == clazz) { /* * get the method from the cache */ method = (VelMethod) icd.thingy; } else { /* * otherwise, do the introspection, and then cache it */ method = node.getRuntimeServices().getUberspect().getMethod(o, methodName, params, new Info(node.getTemplateName(), node.getLine(), node.getColumn())); if (method != null) { icd = new IntrospectionCacheData(); icd.contextData = clazz; icd.thingy = method; context.icachePut(mck, icd); } } /* * if we still haven't gotten the method, either we are calling a method * that doesn't exist (which is fine...) or I screwed it up. */ if (method == null) { if (strictRef) { // Create a parameter list for the exception error message StringBuilder plist = new StringBuilder(); for (int i = 0; i < params.length; i++) { Class param = paramClasses[i]; plist.append(param == null ? "null" : param.getName()); if (i < params.length - 1) plist.append(", "); } throw new MethodInvocationException("Object '" + o.getClass().getName() + "' does not contain method " + methodName + "(" + plist + ")", null, methodName, node .getTemplateName(), node.getLine(), node.getColumn()); } else { return null; } } } catch (MethodInvocationException mie) { /* * this can come from the doIntrospection(), as the arg values are * evaluated to find the right method signature. We just want to propagate * it here, not do anything fancy */ throw mie; } catch (RuntimeException e) { /** * pass through application level runtime exceptions */ throw e; } catch (Exception e) { /* * can come from the doIntropection() also, from Introspector */ String msg = "ASTMethod.execute() : exception from introspection"; throw new VelocityException(msg, e); } return method; } }