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
047    /**
048     * SHIRO-767: add a map to mapping primitive data type
049     */
050    private static final HashMap<String, Class<?>> PRIM_CLASSES
051            = new HashMap<>(8, 1.0F);
052
053    static {
054        PRIM_CLASSES.put("boolean", boolean.class);
055        PRIM_CLASSES.put("byte", byte.class);
056        PRIM_CLASSES.put("char", char.class);
057        PRIM_CLASSES.put("short", short.class);
058        PRIM_CLASSES.put("int", int.class);
059        PRIM_CLASSES.put("long", long.class);
060        PRIM_CLASSES.put("float", float.class);
061        PRIM_CLASSES.put("double", double.class);
062        PRIM_CLASSES.put("void", void.class);
063    }
064
065    /**
066     * @since 1.0
067     */
068    private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
069        @Override
070        protected ClassLoader doGetClassLoader() throws Throwable {
071            return Thread.currentThread().getContextClassLoader();
072        }
073    };
074
075    /**
076     * @since 1.0
077     */
078    private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
079        @Override
080        protected ClassLoader doGetClassLoader() throws Throwable {
081            return ClassUtils.class.getClassLoader();
082        }
083    };
084
085    /**
086     * @since 1.0
087     */
088    private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
089        @Override
090        protected ClassLoader doGetClassLoader() throws Throwable {
091            return ClassLoader.getSystemClassLoader();
092        }
093    };
094
095    private ClassUtils() {
096
097    }
098
099    /**
100     * Returns the specified resource by checking the current thread's
101     * {@link Thread#getContextClassLoader() context class loader}, then the
102     * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application
103     * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order, using
104     * {@link ClassLoader#getResourceAsStream(String) getResourceAsStream(name)}.
105     *
106     * @param name the name of the resource to acquire from the classloader(s).
107     * @return the InputStream of the resource found, or <code>null</code> if the resource cannot be found from any
108     * of the three mentioned ClassLoaders.
109     * @since 0.9
110     */
111    public static InputStream getResourceAsStream(String name) {
112
113        InputStream is = THREAD_CL_ACCESSOR.getResourceStream(name);
114
115        if (is == null) {
116            if (LOGGER.isTraceEnabled()) {
117                LOGGER.trace("Resource [" + name + "] was not found via the thread context ClassLoader.  Trying the "
118                        + "current ClassLoader...");
119            }
120            is = CLASS_CL_ACCESSOR.getResourceStream(name);
121        }
122
123        if (is == null) {
124            if (LOGGER.isTraceEnabled()) {
125                LOGGER.trace("Resource [" + name + "] was not found via the current class loader.  Trying the "
126                        + "system/application ClassLoader...");
127            }
128            is = SYSTEM_CL_ACCESSOR.getResourceStream(name);
129        }
130
131        if (is == null && LOGGER.isTraceEnabled()) {
132            LOGGER.trace("Resource [" + name + "] was not found via the thread context, current, or "
133                    + "system/application ClassLoaders.  All heuristics have been exhausted.  Returning null.");
134        }
135
136        return is;
137    }
138
139    /**
140     * Attempts to load the specified class name from the current thread's
141     * {@link Thread#getContextClassLoader() context class loader}, then the
142     * current ClassLoader (<code>ClassUtils.class.getClassLoader()</code>), then the system/application
143     * ClassLoader (<code>ClassLoader.getSystemClassLoader()</code>, in that order.  If any of them cannot locate
144     * the specified class, an <code>UnknownClassException</code> is thrown (our RuntimeException equivalent of
145     * the JRE's <code>ClassNotFoundException</code>.
146     *
147     * @param fqcn the fully qualified class name to load
148     * @return the located class
149     * @throws UnknownClassException if the class cannot be found.
150     */
151    @SuppressWarnings("unchecked")
152    public static <T> Class<T> forName(String fqcn) throws UnknownClassException {
153        Class<?> clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
154
155        if (clazz == null) {
156            if (LOGGER.isTraceEnabled()) {
157                LOGGER.trace("Unable to load class named [" + fqcn
158                        + "] from the thread context ClassLoader.  Trying the current ClassLoader...");
159            }
160            clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
161        }
162
163        if (clazz == null) {
164            if (LOGGER.isTraceEnabled()) {
165                LOGGER.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  "
166                        + "Trying the system/application ClassLoader...");
167            }
168            clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
169        }
170
171        if (clazz == null) {
172            //SHIRO-767: support for getting primitive data type,such as int,double...
173            clazz = PRIM_CLASSES.get(fqcn);
174        }
175
176        if (clazz == null) {
177            String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or "
178                    + "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
179            throw new UnknownClassException(msg);
180        }
181
182        return (Class<T>) clazz;
183    }
184
185    public static boolean isAvailable(String fullyQualifiedClassName) {
186        try {
187            forName(fullyQualifiedClassName);
188            return true;
189        } catch (UnknownClassException e) {
190            return false;
191        }
192    }
193
194    public static Object newInstance(String fqcn) {
195        return newInstance(forName(fqcn));
196    }
197
198    public static Object newInstance(String fqcn, Object... args) {
199        return newInstance(forName(fqcn), args);
200    }
201
202    public static Object newInstance(Class<?> clazz) {
203        if (clazz == null) {
204            String msg = "Class method parameter cannot be null.";
205            throw new IllegalArgumentException(msg);
206        }
207        try {
208            return clazz.getDeclaredConstructor().newInstance();
209        } catch (Exception e) {
210            throw new InstantiationException("Unable to instantiate class [" + clazz.getName() + "]", e);
211        }
212    }
213
214    public static Object newInstance(Class<?> clazz, Object... args) {
215        var argTypes = new Class<?>[args.length];
216        for (int i = 0; i < args.length; i++) {
217            argTypes[i] = args[i].getClass();
218        }
219        Constructor<?> ctor = getConstructor(clazz, argTypes);
220        return instantiate(ctor, args);
221    }
222
223    public static Constructor<?> getConstructor(Class<?> clazz, Class<?>... argTypes) {
224        try {
225            return clazz.getConstructor(argTypes);
226        } catch (NoSuchMethodException e) {
227            throw new IllegalStateException(e);
228        }
229    }
230
231    public static Object instantiate(Constructor<?> ctor, Object... args) {
232        try {
233            return ctor.newInstance(args);
234        } catch (Exception e) {
235            String msg = "Unable to instantiate Permission instance with constructor [" + ctor + "]";
236            throw new InstantiationException(msg, e);
237        }
238    }
239
240    /**
241     * @param type
242     * @param annotation
243     * @return
244     * @since 1.3
245     */
246    public static List<Method> getAnnotatedMethods(final Class<?> type, final Class<? extends Annotation> annotation) {
247        final List<Method> methods = new ArrayList<>();
248        Class<?> clazz = type;
249        while (!Object.class.equals(clazz)) {
250            Method[] currentClassMethods = clazz.getDeclaredMethods();
251            for (final Method method : currentClassMethods) {
252                if (annotation == null || method.isAnnotationPresent(annotation)) {
253                    methods.add(method);
254                }
255            }
256            // move to the upper class in the hierarchy in search for more methods
257            clazz = clazz.getSuperclass();
258        }
259        return methods;
260    }
261
262    /**
263     * @since 1.0
264     */
265    private interface ClassLoaderAccessor {
266        Class<?> loadClass(String fqcn);
267
268        InputStream getResourceStream(String name);
269    }
270
271    /**
272     * @since 1.0
273     */
274    private abstract static class ExceptionIgnoringAccessor implements ClassLoaderAccessor {
275
276        public Class<?> loadClass(String fqcn) {
277            Class<?> clazz = null;
278            ClassLoader cl = getClassLoader();
279            if (cl != null) {
280                try {
281                    //SHIRO-767: Use Class.forName instead of cl.loadClass(), as byte arrays would fail otherwise.
282                    clazz = Class.forName(fqcn, false, cl);
283                } catch (ClassNotFoundException e) {
284                    if (LOGGER.isTraceEnabled()) {
285                        LOGGER.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
286                    }
287                }
288            }
289            return clazz;
290        }
291
292        public InputStream getResourceStream(String name) {
293            InputStream is = null;
294            ClassLoader cl = getClassLoader();
295            if (cl != null) {
296                is = cl.getResourceAsStream(name);
297            }
298            return is;
299        }
300
301        protected final ClassLoader getClassLoader() {
302            try {
303                return doGetClassLoader();
304            } catch (Throwable t) {
305                if (LOGGER.isDebugEnabled()) {
306                    LOGGER.debug("Unable to acquire ClassLoader.", t);
307                }
308            }
309            return null;
310        }
311
312        protected abstract ClassLoader doGetClassLoader() throws Throwable;
313    }
314}