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