Fix some util javadoc warnings
[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.ParameterizedType;
17 import java.lang.reflect.Type;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.concurrent.Callable;
21 import java.util.function.Function;
22 import java.util.function.Supplier;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 /**
28  * Utility methods for working with ClassLoaders and classes.
29  */
30 public final class ClassLoaderUtils {
31     private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderUtils.class);
32     private static final Joiner DOT_JOINER = Joiner.on(".");
33     private static final Splitter DOT_SPLITTER = Splitter.on('.');
34
35     private ClassLoaderUtils() {
36         // Hidden on purpose
37     }
38
39     /**
40      * Immediately call {@link Function#apply(Object)} with provided {@link ClassLoader}. This method safely switches
41      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
42      * method.
43      *
44      * @param <T> the type of the input to the function
45      * @param <R> the type of the result of the function
46      * @param cls {@link ClassLoader} to be used.
47      * @param function Function to be applied.
48      * @param input Function input
49      * @return Result of function invocation.
50      * @throws NullPointerException if class loader or function is null
51      */
52     @Beta
53     public static <T, R> R applyWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Function<T, R> function,
54             final T input) {
55         final Thread currentThread = Thread.currentThread();
56         final ClassLoader oldCls = currentThread.getContextClassLoader();
57         currentThread.setContextClassLoader(requireNonNull(cls));
58         try {
59             return requireNonNull(function).apply(input);
60         } finally {
61             currentThread.setContextClassLoader(oldCls);
62         }
63     }
64
65     /**
66      * Immediately call {@link Callable#call()} with provided {@link ClassLoader}. This method safely switches
67      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
68      * method.
69      *
70      * @param <V> the result type of the callable
71      * @param cls {@link ClassLoader} to be used.
72      * @param callable Function to be executed.
73      * @return Result of callable invocation.
74      * @throws NullPointerException if class loader or callable is null
75      * @throws Exception if the callable fails
76      */
77     @Beta
78     public static <V> V callWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Callable<V> callable)
79             throws Exception {
80         final Thread currentThread = Thread.currentThread();
81         final ClassLoader oldCls = currentThread.getContextClassLoader();
82         currentThread.setContextClassLoader(requireNonNull(cls));
83         try {
84             return requireNonNull(callable).call();
85         } finally {
86             currentThread.setContextClassLoader(oldCls);
87         }
88     }
89
90     /**
91      * Immediately call {@link Supplier#get()} with provided {@link ClassLoader}. This method safely switches
92      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
93      * method.
94      *
95      * @param <V> the result type of the supplier
96      * @param cls {@link ClassLoader} to be used.
97      * @param supplier Function to be executed.
98      * @return Result of supplier invocation.
99      * @throws NullPointerException if class loader or supplier is null
100      */
101     @Beta
102     public static <V> V getWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Supplier<V> supplier) {
103         final Thread currentThread = Thread.currentThread();
104         final ClassLoader oldCls = currentThread.getContextClassLoader();
105         currentThread.setContextClassLoader(requireNonNull(cls));
106         try {
107             return requireNonNull(supplier).get();
108         } finally {
109             currentThread.setContextClassLoader(oldCls);
110         }
111     }
112
113     /**
114      * Immediately call {@link Runnable#run()} with provided {@link ClassLoader}. This method safely switches
115      * the thread's Thread Context Class Loader to the specified class loader for the duration of execution of that
116      * method.
117      *
118      * @param cls {@link ClassLoader} to be used.
119      * @param runnable Function to be executed.
120      * @throws NullPointerException if class loader or runnable is null
121      */
122     @Beta
123     public static void runWithClassLoader(final @NonNull ClassLoader cls, final @NonNull Runnable runnable) {
124         final Thread currentThread = Thread.currentThread();
125         final ClassLoader oldCls = currentThread.getContextClassLoader();
126         currentThread.setContextClassLoader(requireNonNull(cls));
127         try {
128             requireNonNull(runnable).run();
129         } finally {
130             currentThread.setContextClassLoader(oldCls);
131         }
132     }
133
134     /**
135      * Loads class using this supplied classloader.
136      *
137      * @param cls class loader
138      * @param name String name of class.
139      * @return Loaded class
140      * @throws ClassNotFoundException if the class cannot be loaded
141      * @throws NullPointerException if any argument is null
142      */
143     public static Class<?> loadClass(final ClassLoader cls, final String name) throws ClassNotFoundException {
144         if ("byte[]".equals(name)) {
145             return byte[].class;
146         }
147         if ("char[]".equals(name)) {
148             return char[].class;
149         }
150         return loadClass0(cls, name);
151     }
152
153     private static Class<?> loadClass0(final ClassLoader cls, final String name) throws ClassNotFoundException {
154         try {
155             return cls.loadClass(name);
156         } catch (final ClassNotFoundException e) {
157             final List<String> components = DOT_SPLITTER.splitToList(name);
158
159             if (isInnerClass(components)) {
160                 final int length = components.size() - 1;
161                 final String outerName = DOT_JOINER.join(Iterables.limit(components, length));
162                 final String innerName = outerName + "$" + components.get(length);
163                 return cls.loadClass(innerName);
164             }
165
166             throw e;
167         }
168     }
169
170     private static boolean isInnerClass(final List<String> components) {
171         final int length = components.size();
172         if (length < 2) {
173             return false;
174         }
175
176         final String potentialOuter = components.get(length - 2);
177         if (potentialOuter == null) {
178             return false;
179         }
180         return Character.isUpperCase(potentialOuter.charAt(0));
181     }
182
183     public static Class<?> loadClassWithTCCL(final String name) throws ClassNotFoundException {
184         final Thread thread = Thread.currentThread();
185         final ClassLoader tccl = thread.getContextClassLoader();
186         if (tccl == null) {
187             throw new ClassNotFoundException("Thread " + thread + " does not have a Context Class Loader, cannot load "
188                     + name);
189         }
190         return loadClass(tccl, name);
191     }
192
193     public static Optional<Class<?>> tryToLoadClassWithTCCL(final String fullyQualifiedClassName) {
194         final Thread thread = Thread.currentThread();
195         final ClassLoader tccl = thread.getContextClassLoader();
196         if (tccl == null) {
197             LOG.debug("Thread {} does not have a Context Class Loader, not loading class {}", thread,
198                 fullyQualifiedClassName);
199             return Optional.empty();
200         }
201
202         try {
203             return Optional.of(loadClass(tccl, fullyQualifiedClassName));
204         } catch (final ClassNotFoundException e) {
205             LOG.debug("Failed to load class {}", fullyQualifiedClassName, e);
206             return Optional.empty();
207         }
208     }
209
210     @SuppressWarnings("unchecked")
211     public static <S, G, P> Optional<Class<P>> findFirstGenericArgument(final Class<S> scannedClass,
212             final Class<G> genericType) {
213         return getWithClassLoader(scannedClass.getClassLoader(), () -> {
214             return findParameterizedType(scannedClass, genericType)
215                     .map(ptype -> (Class<P>) ptype.getActualTypeArguments()[0]);
216         });
217     }
218
219     /**
220      * Find the parameterized instantiation of a particular interface implemented by a class.
221      *
222      * @param subclass Implementing class
223      * @param genericType Interface to search for
224      * @return Parameterized interface as implemented by the class, if present
225      */
226     public static Optional<ParameterizedType> findParameterizedType(final Class<?> subclass,
227             final Class<?> genericType) {
228         requireNonNull(genericType);
229
230         for (final Type type : subclass.getGenericInterfaces()) {
231             if (type instanceof ParameterizedType) {
232                 final ParameterizedType ptype = (ParameterizedType) type;
233                 if (genericType.equals(ptype.getRawType())) {
234                     return Optional.of(ptype);
235                 }
236             }
237         }
238
239         LOG.debug("Class {} does not declare interface {}", subclass, genericType);
240         return Optional.empty();
241     }
242
243     /**
244      * Extract the first generic type argument for a Type. If the type is not parameterized, this method returns empty.
245      *
246      * @param type Type to examine
247      * @return First generic type argument, if present
248      * @throws NullPointerException if {@code type} is null
249      */
250     public static Optional<Type> getFirstGenericParameter(final Type type) {
251         requireNonNull(type);
252         return type instanceof ParameterizedType ? Optional.of(((ParameterizedType) type).getActualTypeArguments()[0])
253                 : Optional.empty();
254     }
255 }