View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.lang.util;
20  
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import java.io.InputStream;
25  import java.lang.annotation.Annotation;
26  import java.lang.reflect.Constructor;
27  import java.lang.reflect.Method;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  
32  
33  /**
34   * Utility method library used to conveniently interact with <code>Class</code>es, such as acquiring them from the
35   * application <code>ClassLoader</code>s and instantiating Objects from them.
36   *
37   * @since 0.1
38   */
39  public final class ClassUtils {
40  
41      /**
42       * Private internal log instance.
43       */
44      private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class);
45  
46  
47      /**
48       * SHIRO-767: add a map to mapping primitive data type
49       */
50      private static final HashMap<String, Class<?>> PRIM_CLASSES
51              = new HashMap<>(8, 1.0F);
52  
53      static {
54          PRIM_CLASSES.put("boolean", boolean.class);
55          PRIM_CLASSES.put("byte", byte.class);
56          PRIM_CLASSES.put("char", char.class);
57          PRIM_CLASSES.put("short", short.class);
58          PRIM_CLASSES.put("int", int.class);
59          PRIM_CLASSES.put("long", long.class);
60          PRIM_CLASSES.put("float", float.class);
61          PRIM_CLASSES.put("double", double.class);
62          PRIM_CLASSES.put("void", void.class);
63      }
64  
65      /**
66       * @since 1.0
67       */
68      private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
69          @Override
70          protected ClassLoader doGetClassLoader() throws Throwable {
71              return Thread.currentThread().getContextClassLoader();
72          }
73      };
74  
75      /**
76       * @since 1.0
77       */
78      private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
79          @Override
80          protected ClassLoader doGetClassLoader() throws Throwable {
81              return ClassUtils.class.getClassLoader();
82          }
83      };
84  
85      /**
86       * @since 1.0
87       */
88      private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
89          @Override
90          protected ClassLoader doGetClassLoader() throws Throwable {
91              return ClassLoader.getSystemClassLoader();
92          }
93      };
94  
95      private ClassUtils() {
96  
97      }
98  
99      /**
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 }