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