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