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}