aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/util/ClassUtils.java
blob: 34ddf5564642fa94248f52382b829035a10b93dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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 <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
 *  @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
     * <code>Class.forName( claz )</code> (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 <code>Class.forName( claz ).newInstance()</code> (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 <code>Class.getResourceAsString</code> 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;
    }

}