Improve ClassLoaderUtils.loadClassWithTCCL()
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / ClassLoaderUtils.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.util;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.Joiner;
14 import com.google.common.base.Splitter;
15 import com.google.common.collect.Iterables;
16 import java.lang.reflect.Constructor;
17 import java.lang.reflect.InvocationTargetException;
18 import java.lang.reflect.ParameterizedType;
19 import java.lang.reflect.Type;
20 import java.util.List;
21 import java.util.concurrent.Callable;
22 import java.util.function.Function;
23 import java.util.function.Supplier;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 /**
29  * Utility methods for working with ClassLoaders and classes.
30  */
31 public final class ClassLoaderUtils {
32     private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderUtils.class);
33     private static final Joiner DOT_JOINER = Joiner.on(".");
34     private static final Splitter DOT_SPLITTER = Splitter.on('.');
35
36     private ClassLoaderUtils() {
37         throw new UnsupportedOperationException("Utility class");
38     }
39
40     /**
41      * Immediately call {@link Function#apply(Object)} with provided {@link ClassLoader}. This method safely switches
42      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
43      * method.
44      *
45      * @param cls {@link ClassLoader} to be used.
46      * @param function Function to be applied.
47      * @param input Function input
48      * @throws NullPointerException if class loader or function is null
49      */
50     @Beta
51     public static <T, R> R applyWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Function<T, R> function,
52             final T input) {
53         final Thread currentThread = Thread.currentThread();
54         final ClassLoader oldCls = currentThread.getContextClassLoader();
55         currentThread.setContextClassLoader(requireNonNull(cls));
56         try {
57             return requireNonNull(function).apply(input);
58         } finally {
59             currentThread.setContextClassLoader(oldCls);
60         }
61     }
62
63     /**
64      * Immediately call {@link Callable#call()} with provided {@link ClassLoader}. This method safely switches
65      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
66      * method.
67      *
68      * @param cls {@link ClassLoader} to be used.
69      * @param callable Function to be executed.
70      * @return Result of callable invocation.
71      * @throws NullPointerException if class loader or callable is null
72      */
73     @Beta
74     public static <V> V callWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Callable<V> callable)
75             throws Exception {
76         final Thread currentThread = Thread.currentThread();
77         final ClassLoader oldCls = currentThread.getContextClassLoader();
78         currentThread.setContextClassLoader(requireNonNull(cls));
79         try {
80             return requireNonNull(callable).call();
81         } finally {
82             currentThread.setContextClassLoader(oldCls);
83         }
84     }
85
86     /**
87      * Immediately call {@link Supplier#get()} with provided {@link ClassLoader}. This method safely switches
88      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
89      * method.
90      *
91      * @param cls {@link ClassLoader} to be used.
92      * @param supplier Function to be executed.
93      * @return Result of supplier invocation.
94      * @throws NullPointerException if class loader or supplier is null
95      */
96     @Beta
97     public static <V> V getWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Supplier<V> supplier) {
98         final Thread currentThread = Thread.currentThread();
99         final ClassLoader oldCls = currentThread.getContextClassLoader();
100         currentThread.setContextClassLoader(requireNonNull(cls));
101         try {
102             return requireNonNull(supplier).get();
103         } finally {
104             currentThread.setContextClassLoader(oldCls);
105         }
106     }
107
108     /**
109      * Immediately call {@link Runnable#run()} with provided {@link ClassLoader}. This method safely switches
110      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
111      * method.
112      *
113      * @param cls {@link ClassLoader} to be used.
114      * @param runnable Function to be executed.
115      * @throws NullPointerException if class loader or runnable is null
116      */
117     @Beta
118     public static void runWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Runnable runnable) {
119         final Thread currentThread = Thread.currentThread();
120         final ClassLoader oldCls = currentThread.getContextClassLoader();
121         currentThread.setContextClassLoader(requireNonNull(cls));
122         try {
123             requireNonNull(runnable).run();
124         } finally {
125             currentThread.setContextClassLoader(oldCls);
126         }
127     }
128
129     /**
130      * Runs {@link Supplier} with provided {@link ClassLoader}.
131      *
132      * <p>
133      * Invokes supplies function and makes sure that original {@link ClassLoader}
134      * is context {@link ClassLoader} after execution.
135      *
136      * @param cls {@link ClassLoader} to be used.
137      * @param function Function to be executed.
138      * @return Result of supplier invocation.
139      *
140      * @deprecated Use {@link #getWithClassLoader(ClassLoader, Supplier)} instead.
141      */
142     @Deprecated
143     public static <V> V withClassLoader(final ClassLoader cls, final Supplier<V> function) {
144         return getWithClassLoader(cls, function);
145     }
146
147     /**
148      * Runs {@link Callable} with provided {@link ClassLoader}.
149      *
150      * <p>
151      * Invokes supplies function and makes sure that original {@link ClassLoader}
152      * is context {@link ClassLoader} after execution.
153      *
154      * @param cls {@link ClassLoader} to be used.
155      * @param function Function to be executed.
156      * @return Result of callable invocation.
157      *
158      * @deprecated Use {@link #callWithClassLoader(ClassLoader, Callable)} instead.
159      */
160     @Deprecated
161     public static <V> V withClassLoader(final ClassLoader cls, final Callable<V> function) throws Exception {
162         return callWithClassLoader(cls, function);
163     }
164
165     // FIXME: 3.0.0: Remove or improve this to be an explicit cast to a receiver <T>?
166     public static Object construct(final Constructor<?> constructor, final List<Object> objects)
167             throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
168         final Object[] initargs = objects.toArray();
169         return constructor.newInstance(initargs);
170     }
171
172     /**
173      * Loads class using this supplied classloader.
174      *
175      * @param name String name of class.
176      */
177     public static Class<?> loadClass(final ClassLoader cls, final String name) throws ClassNotFoundException {
178         if ("byte[]".equals(name)) {
179             return byte[].class;
180         }
181         if ("char[]".equals(name)) {
182             return char[].class;
183         }
184         return loadClass0(cls,name);
185     }
186
187     private static Class<?> loadClass0(final ClassLoader cls, final String name) throws ClassNotFoundException {
188         try {
189             return cls.loadClass(name);
190         } catch (final ClassNotFoundException e) {
191             final List<String> components = DOT_SPLITTER.splitToList(name);
192
193             if (isInnerClass(components)) {
194                 final int length = components.size() - 1;
195                 final String outerName = DOT_JOINER.join(Iterables.limit(components, length));
196                 final String innerName = outerName + "$" + components.get(length);
197                 return cls.loadClass(innerName);
198             }
199
200             throw e;
201         }
202     }
203
204     private static boolean isInnerClass(final List<String> components) {
205         final int length = components.size();
206         if (length < 2) {
207             return false;
208         }
209
210         final String potentialOuter = components.get(length - 2);
211         if (potentialOuter == null) {
212             return false;
213         }
214         return Character.isUpperCase(potentialOuter.charAt(0));
215     }
216
217     public static Class<?> loadClassWithTCCL(final String name) throws ClassNotFoundException {
218         final Thread thread = Thread.currentThread();
219         final ClassLoader tccl = thread.getContextClassLoader();
220         if (tccl == null) {
221             throw new ClassNotFoundException("Thread " + thread + " does not have a Context Class Loader, cannot load "
222                     + name);
223         }
224         return loadClass(tccl, name);
225     }
226
227     // FIXME: 3.0.0: Document and return Optional
228     public static Class<?> tryToLoadClassWithTCCL(final String fullyQualifiedClassName) {
229         final Thread thread = Thread.currentThread();
230         final ClassLoader tccl = thread.getContextClassLoader();
231         if (tccl == null) {
232             LOG.debug("Thread {} does not have a Context Class Loader, not loading class {}", thread,
233                 fullyQualifiedClassName);
234             return null;
235         }
236
237         try {
238             return loadClass(tccl, fullyQualifiedClassName);
239         } catch (final ClassNotFoundException e) {
240             LOG.debug("Failed to load class {}", fullyQualifiedClassName, e);
241             return null;
242         }
243     }
244
245     public static <S,G,P> Class<P> findFirstGenericArgument(final Class<S> scannedClass, final Class<G> genericType) {
246         return withClassLoader(scannedClass.getClassLoader(), findFirstGenericArgumentTask(scannedClass, genericType));
247     }
248
249     @SuppressWarnings("unchecked")
250     private static <S, G, P> Supplier<Class<P>> findFirstGenericArgumentTask(final Class<S> scannedClass,
251             final Class<G> genericType) {
252         return () -> {
253             final ParameterizedType augmentationGeneric = findParameterizedType(scannedClass, genericType);
254             if (augmentationGeneric != null) {
255                 return (Class<P>) augmentationGeneric.getActualTypeArguments()[0];
256             }
257             return null;
258         };
259     }
260
261     // FIXME: 3.0.0: Document and return Optional
262     public static ParameterizedType findParameterizedType(final Class<?> subclass, final Class<?> genericType) {
263         requireNonNull(subclass);
264         requireNonNull(genericType);
265
266         for (final Type type : subclass.getGenericInterfaces()) {
267             if (type instanceof ParameterizedType && genericType.equals(((ParameterizedType) type).getRawType())) {
268                 return (ParameterizedType) type;
269             }
270         }
271
272         LOG.debug("Class {} does not declare interface {}", subclass, genericType);
273         return null;
274     }
275
276     // FIXME: 3.0.0: Document and return Optional
277     public static Type getFirstGenericParameter(final Type type) {
278         if (type instanceof ParameterizedType) {
279             return ((ParameterizedType) type).getActualTypeArguments()[0];
280         }
281         return null;
282     }
283 }