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