001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.lang.util;
020
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024import java.io.InputStream;
025import java.lang.annotation.Annotation;
026import java.lang.reflect.Constructor;
027import java.lang.reflect.Method;
028import java.util.ArrayList;
029import java.util.HashMap;
030import java.util.List;
031
032
033/**
034 * Utility method library used to conveniently interact with <code>Class</code>es, such as acquiring them from the
035 * application <code>ClassLoader</code>s and instantiating Objects from them.
036 *
037 * @since 0.1
038 */
039public final class ClassUtils {
040
041    /**
042     * Private internal log instance.
043     */
044    private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class);
045
046    private static final ThreadLocal<ClassLoader> ADDITIONAL_CLASS_LOADER = new ThreadLocal<>();
047
048    /**
049     * SHIRO-767: add a map to mapping primitive data type
050     */
051    private static final HashMap<String, Class<?>> PRIM_CLASSES
052            = new HashMap<>(8, 1.0F);
053
054    static {
055        PRIM_CLASSES.put("boolean", boolean.class);
056        PRIM_CLASSES.put("byte", byte.class);
057        PRIM_CLASSES.put("char", char.class);
058        PRIM_CLASSES.put("short", short.class);
059        PRIM_CLASSES.put("int", int.class);
060        PRIM_CLASSES.put("long", long.class);
061        PRIM_CLASSES.put("float", float.class);
062        PRIM_CLASSES.put("double", double.class);
063        PRIM_CLASSES.put("void", void.class);
064    }
065
066    /**
067     * @since 1.0
068     */
069    private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
070        @Override
071        protected ClassLoader doGetClassLoader() throws Throwable {
072            return Thread.currentThread().getContextClassLoader();
073        }
074    };
075
076    /**
077     * @since 1.0
078     */
079    private static final ClassLoaderAccessor CLASS_LANG_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
080        @Override
081        protected ClassLoader doGetClassLoader() throws Throwable {
082            return ClassUtils.class.getClassLoader();
083        }
084    };
085
086    /**
087     * @since 2.0.4
088     */
089    private static final ClassLoaderAccessor ADDITIONAL_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
090        @Override
091        protected ClassLoader doGetClassLoader() throws Throwable {
092            ClassLoader cl = ADDITIONAL_CLASS_LOADER.get();
093            return cl != null ? cl : ClassUtils.class.getClassLoader();
094        }
095    };
096
097    /**
098     * @since 1.0
099     */
100    private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
101        @Override
102        protected ClassLoader doGetClassLoader() throws Throwable {
103            return ClassLoader.getSystemClassLoader();
104        }
105    };
106
107    private ClassUtils() {
108
109    }
110
111    /**
112     * Returns the specified resource by checking the current thread's
113     * {@link Thread#getContextClassLoader() context class loader}, then the
114     * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application
115     * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order, using
116     * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}.
117     *
118     * @param name the name of the resource to acquire from the classloader(s).
119     * @return the InputStream of the resource found, or <code>null</code> if the resource cannot be found from any
120     * of the three mentioned ClassLoaders.
121     * @since 0.9
122     */
123    public static InputStream getResourceAsStream(String name) {
124
125        InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
126
127        if (is == null) {
128            if (LOGGER.isTraceEnabled()) {
129                LOGGER.trace("Resource [" + name + "] was not found via the thread context ClassLoader.  Trying the "
130                        + "current ClassLoader...");
131            }
132            is = CLASS_LANG_CL_ACCESSOR.getResourceStream(name);
133        }
134
135        if (is == null) {
136            if (LOGGER.isTraceEnabled()) {
137                LOGGER.trace("Resource [" + name + "] was not found via the org.apache.shiro.lang ClassLoader.  Trying the "
138                        + "additionally set ClassLoader...");
139            }
140            is = ADDITIONAL_CL_ACCESSOR.getResourceStream(name);
141        }
142
143        if (is == null) {
144            if (LOGGER.isTraceEnabled()) {
145                LOGGER.trace("Resource [" + name + "] was not found via the current class loader.  Trying the "
146                        + "system/application ClassLoader...");
147            }
148            is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
149        }
150
151        if (is == null && LOGGER.isTraceEnabled()) {
152            LOGGER.trace("Resource [" + name + "] was not found via the thread context, current, or "
153                    + "system/application ClassLoaders.  All heuristics have been exhausted.  Returning null.");
154        }
155
156        return is;
157    }
158
159    /**
160     * Attempts to load the specified class name from the current thread's
161     * {@link Thread#getContextClassLoader() context class loader}, then the
162     * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application
163     * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order.  If any of them cannot locate
164     * the specified class, an <code>UnknownClassException</code> is thrown (our RuntimeException equivalent of
165     * the JRE's <code>ClassNotFoundException</code>.
166     *
167     * @param fqcn the fully qualified class name to load
168     * @return the located class
169     * @throws UnknownClassException if the class cannot be found.
170     */
171    @SuppressWarnings("unchecked")
172    public static <T> Class<T> forName(String fqcn) throws UnknownClassException {
173        Class<?> clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
174
175        if (clazz == null) {
176            if (LOGGER.isTraceEnabled()) {
177                LOGGER.trace("Unable to load class named [" + fqcn
178                        + "] from the thread context ClassLoader.  Trying the current ClassLoader...");
179            }
180            clazz = CLASS_LANG_CL_ACCESSOR.loadClass(fqcn);
181        }
182
183        if (clazz == null) {
184            if (LOGGER.isTraceEnabled()) {
185                LOGGER.trace("Unable to load class named [" + fqcn
186                        + "] from the org.apache.shiro.lang ClassLoader.  Trying the additionally set ClassLoader...");
187            }
188            clazz = ADDITIONAL_CL_ACCESSOR.loadClass(fqcn);
189        }
190
191        if (clazz == null) {
192            if (LOGGER.isTraceEnabled()) {
193                LOGGER.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  "
194                        + "Trying the system/application ClassLoader...");
195            }
196            clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
197        }
198
199        if (clazz == null) {
200            //SHIRO-767: support for getting primitive data type,such as int,double...
201            clazz = PRIM_CLASSES.get(fqcn);
202        }
203
204        if (clazz == null) {
205            String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or "
206                    + "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
207            throw new UnknownClassException(msg);
208        }
209
210        return (Class<T>) clazz;
211    }
212
213    public static boolean isAvailable(String fullyQualifiedClassName) {
214        try {
215            forName(fullyQualifiedClassName);
216            return true;
217        } catch (UnknownClassException e) {
218            return false;
219        }
220    }
221
222    public static Object newInstance(String fqcn) {
223        return newInstance(forName(fqcn));
224    }
225
226    public static Object newInstance(String fqcn, Object... args) {
227        return newInstance(forName(fqcn), args);
228    }
229
230    public static Object newInstance(Class<?> clazz) {
231        if (clazz == null) {
232            String msg = "Class method parameter cannot be null.";
233            throw new IllegalArgumentException(msg);
234        }
235        try {
236            return clazz.getDeclaredConstructor().newInstance();
237        } catch (Exception e) {
238            throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e);
239        }
240    }
241
242    public static Object newInstance(Class<?> clazz, Object... args) {
243        var argTypes = new Class<?>[args.length];
244        for (int i = 0; i < args.length; i++) {
245            argTypes[i] = args[i].getClass();
246        }
247        Constructor<?> ctor = getConstructor(clazz, argTypes);
248        return instantiate(ctor, args);
249    }
250
251    public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... argTypes) {
252        try {
253            return clazz.getConstructor(argTypes);
254        } catch (NoSuchMethodException e) {
255            throw new IllegalStateException(e);
256        }
257    }
258
259    public static Object instantiate(Constructor<?> ctor, Object... args) {
260        try {
261            return ctor.newInstance(args);
262        } catch (Exception e) {
263            String msg = "Unable to instantiate Permission instance with constructor [" + ctor + "]";
264            throw new InstantiationException(msg, e);
265        }
266    }
267
268    /**
269     * @param type
270     * @param annotation
271     * @return
272     * @since 1.3
273     */
274    public static List<Method> getAnnotatedMethods(final Class<?> type, final Class<? extends Annotation> annotation) {
275        final List<Method> methods = new ArrayList<>();
276        Class<?> clazz = type;
277        while (!Object.class.equals(clazz)) {
278            Method[] currentClassMethods = clazz.getDeclaredMethods();
279            for (final Method method : currentClassMethods) {
280                if (annotation == null || method.isAnnotationPresent(annotation)) {
281                    methods.add(method);
282                }
283            }
284            // move to the upper class in the hierarchy in search for more methods
285            clazz = clazz.getSuperclass();
286        }
287        return methods;
288    }
289
290    /**
291     * Sets additional ClassLoader for {@link #getResourceAsStream(String)} and {@link #forName(String)} to use
292     * It is used in addition to the thread context class loader and the system class loader.
293
294     * @param classLoader class loader to use
295     * @since 2.0.4
296     */
297    public static void setAdditionalClassLoader(ClassLoader classLoader) {
298        ADDITIONAL_CLASS_LOADER.set(classLoader);
299    }
300
301    /**
302     * Removes the additional ClassLoader set by {@link #setAdditionalClassLoader(ClassLoader)}.
303     * This must be called to avoid memory leaks.
304     *
305     * @since 2.0.4
306     */
307    public static void removeAdditionalClassLoader() {
308        ADDITIONAL_CLASS_LOADER.remove();
309    }
310
311    /**
312     * @since 1.0
313     */
314    private interface ClassLoaderAccessor {
315        Class<?> loadClass(String fqcn);
316
317        InputStream getResourceStream(String name);
318    }
319
320    /**
321     * @since 1.0
322     */
323    private abstract static class ExceptionIgnoringAccessor implements ClassLoaderAccessor {
324
325        public Class<?> loadClass(String fqcn) {
326            Class<?> clazz = null;
327            ClassLoader cl = getClassLoader();
328            if (cl != null) {
329                try {
330                    //SHIRO-767: Use Class.forName instead of cl.loadClass(), as byte arrays would fail otherwise.
331                    clazz = Class.forName(fqcn, false, cl);
332                } catch (ClassNotFoundException e) {
333                    if (LOGGER.isTraceEnabled()) {
334                        LOGGER.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
335                    }
336                }
337            }
338            return clazz;
339        }
340
341        public InputStream getResourceStream(String name) {
342            InputStream is = null;
343            ClassLoader cl = getClassLoader();
344            if (cl != null) {
345                is = cl.getResourceAsStream(name);
346            }
347            return is;
348        }
349
350        protected final ClassLoader getClassLoader() {
351            try {
352                return doGetClassLoader();
353            } catch (Throwable t) {
354                if (LOGGER.isDebugEnabled()) {
355                    LOGGER.debug("Unable to acquire ClassLoader.", t);
356                }
357            }
358            return null;
359        }
360
361        protected abstract ClassLoader doGetClassLoader() throws Throwable;
362    }
363}