Remove unneeded doc dependency
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / loader / CodecClassLoader.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.mdsal.binding.dom.codec.impl.loader;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.annotations.Beta;
15 import com.google.common.base.Strings;
16 import com.google.common.base.Supplier;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableSet;
19 import java.io.File;
20 import java.io.IOException;
21 import java.security.AccessController;
22 import java.security.PrivilegedAction;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import net.bytebuddy.description.type.TypeDescription;
28 import net.bytebuddy.dynamic.DynamicType.Unloaded;
29 import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * A ClassLoader hosting types generated for a particular type. A root instance is attached to a
36  * BindingCodecContext instance, so any generated classes from it can be garbage-collected when the context
37  * is destroyed, as well as to prevent two contexts trampling over each other.
38  *
39  * <p>
40  * It semantically combines two class loaders: the class loader in which this class is loaded and the class loader in
41  * which a target Binding interface/class is loaded. This inherently supports multi-classloader environments -- the root
42  * instance has visibility only into codec classes and for each classloader we encounter when presented with a binding
43  * class we create a leaf instance and cache it in the root instance. Leaf instances are using the root loader as their
44  * parent, but consult the binding class's class loader if the root loader fails to load a particular class.
45  *
46  * <p>In single-classloader environments, obviously, the root loader can load all binding classes, and hence no leaf
47  * loader is created.
48  *
49  * @author Robert Varga
50  */
51 @Beta
52 public abstract class CodecClassLoader extends ClassLoader {
53     public interface ClassGenerator<T> {
54         /**
55          * Generate a class.
56          *
57          * @param fqcn Generated class Fully-qualified class name
58          * @param bindingInterface Binding interface for which the class is being generated
59          * @return A result.
60          */
61         GeneratorResult<T> generateClass(CodecClassLoader loader, String fqcn, Class<?> bindingInterface);
62
63         /**
64          * Run the specified loader in a customized environment. The environment customizations must be cleaned up by
65          * the time this method returns. The default implementation performs no customization.
66          *
67          * @param loader Class loader to execute
68          * @return Class returned by the loader
69          */
70         default Class<T> customizeLoading(final @NonNull Supplier<Class<T>> loader) {
71             return loader.get();
72         }
73     }
74
75     public static final class GeneratorResult<T> {
76         private final @NonNull ImmutableSet<Class<?>> dependecies;
77         private final @NonNull Unloaded<T> result;
78
79         GeneratorResult(final Unloaded<T> result, final ImmutableSet<Class<?>> dependecies) {
80             this.result = requireNonNull(result);
81             this.dependecies = requireNonNull(dependecies);
82         }
83
84         public static <T> @NonNull GeneratorResult<T> of(final Unloaded<T> result) {
85             return new GeneratorResult<>(result, ImmutableSet.of());
86         }
87
88         public static <T> @NonNull GeneratorResult<T> of(final Unloaded<T> result,
89                 final Collection<Class<?>> dependencies) {
90             return dependencies.isEmpty() ? of(result) : new GeneratorResult<>(result,
91                     ImmutableSet.copyOf(dependencies));
92         }
93
94         @NonNull Unloaded<T> getResult() {
95             return result;
96         }
97
98         @NonNull ImmutableSet<Class<?>> getDependencies() {
99             return dependecies;
100         }
101     }
102
103     private static final ClassLoadingStrategy<CodecClassLoader> STRATEGY = (classLoader, types) -> {
104         verify(types.size() == 1, "Unexpected multiple types", types);
105         final Entry<TypeDescription, byte[]> entry = types.entrySet().iterator().next();
106         return ImmutableMap.of(entry.getKey(), classLoader.loadClass(entry.getKey().getName(), entry.getValue()));
107     };
108
109     static {
110         verify(ClassLoader.registerAsParallelCapable());
111     }
112
113     private static final Logger LOG = LoggerFactory.getLogger(CodecClassLoader.class);
114     private static final File BYTECODE_DIRECTORY;
115
116     static {
117         final String dir = System.getProperty("org.opendaylight.mdsal.binding.dom.codec.loader.bytecodeDumpDirectory");
118         BYTECODE_DIRECTORY = Strings.isNullOrEmpty(dir) ? null : new File(dir);
119     }
120
121     CodecClassLoader(final ClassLoader parentLoader) {
122         super(parentLoader);
123     }
124
125     /**
126      * Instantiate a new CodecClassLoader, which serves as the root of generated code loading.
127      *
128      * @return A new CodecClassLoader.
129      */
130     public static @NonNull CodecClassLoader create() {
131         return AccessController.doPrivileged((PrivilegedAction<CodecClassLoader>)() -> new RootCodecClassLoader());
132     }
133
134     /**
135      * The name of the target class is formed through concatenation of the name of a {@code bindingInterface} and
136      * specified {@code suffix}.
137      *
138      * @param bindingInterface Binding compile-time-generated interface
139      * @param suffix Suffix to use
140      * @param generator Code generator to run
141      * @return A generated class object
142      * @throws NullPointerException if any argument is null
143      */
144     public final <T> Class<T> generateClass(final Class<?> bindingInterface,
145             final String suffix, final ClassGenerator<T> generator)  {
146         return findClassLoader(requireNonNull(bindingInterface)).doGenerateClass(bindingInterface, suffix, generator);
147     }
148
149     public final @NonNull Class<?> getGeneratedClass(final Class<?> bindingInterface, final String suffix) {
150         final CodecClassLoader loader = findClassLoader(requireNonNull(bindingInterface));
151         final String fqcn = generatedClassName(bindingInterface, suffix);
152
153         final Class<?> ret;
154         synchronized (loader.getClassLoadingLock(fqcn)) {
155             ret = loader.findLoadedClass(fqcn);
156         }
157
158         checkArgument(ret != null, "Failed to find generated class %s for %s of %s", fqcn, suffix, bindingInterface);
159         return ret;
160     }
161
162     /**
163      * Append specified loaders to this class loader for the purposes of looking up generated classes. Note that the
164      * loaders are expected to have required classes already loaded. This is required to support generation of
165      * inter-dependent structures, such as those used for streaming binding interfaces.
166      *
167      * @param newLoaders Loaders to append
168      * @throws NullPointerException if {@code loaders} is null
169      */
170     abstract void appendLoaders(@NonNull Set<LeafCodecClassLoader> newLoaders);
171
172     /**
173      * Find the loader responsible for holding classes related to a binding class.
174      *
175      * @param bindingClass Class to locate
176      * @return a Loader instance
177      * @throws NullPointerException if {@code bindingClass} is null
178      */
179     abstract @NonNull CodecClassLoader findClassLoader(@NonNull Class<?> bindingClass);
180
181     private <T> Class<T> doGenerateClass(final Class<?> bindingInterface, final String suffix,
182             final ClassGenerator<T> generator)  {
183         final String fqcn = generatedClassName(bindingInterface, suffix);
184
185         synchronized (getClassLoadingLock(fqcn)) {
186             // Attempt to find a loaded class
187             final Class<?> existing = findLoadedClass(fqcn);
188             if (existing != null) {
189                 return (Class<T>) existing;
190             }
191
192             final GeneratorResult<T> result = generator.generateClass(this, fqcn, bindingInterface);
193             final Unloaded<T> unloaded = result.getResult();
194             verify(fqcn.equals(unloaded.getTypeDescription().getName()), "Unexpected class in %s", unloaded);
195             verify(unloaded.getAuxiliaryTypes().isEmpty(), "Auxiliary types present in %s", unloaded);
196             dumpBytecode(unloaded);
197
198             processDependencies(result.getDependencies());
199             return generator.customizeLoading(() -> (Class<T>) unloaded.load(this, STRATEGY).getLoaded());
200         }
201     }
202
203     final Class<?> loadClass(final String fqcn, final byte[] byteCode) {
204         synchronized (getClassLoadingLock(fqcn)) {
205             final Class<?> existing = findLoadedClass(fqcn);
206             verify(existing == null, "Attempted to load existing %s", existing);
207             return defineClass(fqcn, byteCode, 0, byteCode.length);
208         }
209     }
210
211     private void processDependencies(final Collection<Class<?>> deps) {
212         final Set<LeafCodecClassLoader> depLoaders = new HashSet<>();
213         for (Class<?> dep : deps) {
214             final ClassLoader depLoader = dep.getClassLoader();
215             verify(depLoader instanceof CodecClassLoader, "Dependency %s is not a generated class", dep);
216             if (this.equals(depLoader)) {
217                 // Same loader, skip
218                 continue;
219             }
220
221             try {
222                 loadClass(dep.getName());
223             } catch (ClassNotFoundException e) {
224                 LOG.debug("Cannot find {} in local loader, attempting to compensate", dep, e);
225                 // Root loader is always visible from a leaf, hence the dependency can only be a leaf
226                 verify(depLoader instanceof LeafCodecClassLoader, "Dependency loader %s is not a leaf", depLoader);
227                 depLoaders.add((LeafCodecClassLoader) depLoader);
228             }
229         }
230
231         if (!depLoaders.isEmpty()) {
232             appendLoaders(depLoaders);
233         }
234     }
235
236     private static void dumpBytecode(final Unloaded<?> unloaded) {
237         if (BYTECODE_DIRECTORY != null) {
238             try {
239                 unloaded.saveIn(BYTECODE_DIRECTORY);
240             } catch (IOException | IllegalArgumentException e) {
241                 LOG.info("Failed to save {}", unloaded.getTypeDescription().getName(), e);
242             }
243         }
244     }
245
246     private static String generatedClassName(final Class<?> bindingInterface, final String suffix) {
247         return bindingInterface.getName() + "$$$" + suffix;
248     }
249 }