2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.codec.loader;
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.Strings;
15 import com.google.common.base.Supplier;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.ImmutableSet;
19 import java.io.IOException;
20 import java.security.AccessController;
21 import java.security.PrivilegedAction;
22 import java.util.Collection;
23 import java.util.HashSet;
24 import java.util.Map.Entry;
26 import net.bytebuddy.description.type.TypeDescription;
27 import net.bytebuddy.dynamic.DynamicType.Unloaded;
28 import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * A ClassLoader hosting types generated for a particular type. A root instance is attached to a
35 * BindingCodecContext instance, so any generated classes from it can be garbage-collected when the context
36 * is destroyed, as well as to prevent two contexts trampling over each other.
39 * It semantically combines two class loaders: the class loader in which this class is loaded and the class loader in
40 * which a target Binding interface/class is loaded. This inherently supports multi-classloader environments -- the root
41 * instance has visibility only into codec classes and for each classloader we encounter when presented with a binding
42 * class we create a leaf instance and cache it in the root instance. Leaf instances are using the root loader as their
43 * parent, but consult the binding class's class loader if the root loader fails to load a particular class.
45 * <p>In single-classloader environments, obviously, the root loader can load all binding classes, and hence no leaf
48 * @author Robert Varga
51 public abstract class CodecClassLoader extends ClassLoader {
52 public interface ClassGenerator<T> {
56 * @param fqcn Generated class Fully-qualified class name
57 * @param bindingInterface Binding interface for which the class is being generated
60 GeneratorResult<T> generateClass(CodecClassLoader loader, String fqcn, Class<?> bindingInterface);
63 * Run the specified loader in a customized environment. The environment customizations must be cleaned up by
64 * the time this method returns. The default implementation performs no customization.
66 * @param loader Class loader to execute
67 * @return Class returned by the loader
69 default Class<T> customizeLoading(final @NonNull Supplier<Class<T>> loader) {
74 public static final class GeneratorResult<T> {
75 private final @NonNull ImmutableSet<Class<?>> dependecies;
76 private final @NonNull Unloaded<T> result;
78 GeneratorResult(final Unloaded<T> result, final ImmutableSet<Class<?>> dependecies) {
79 this.result = requireNonNull(result);
80 this.dependecies = requireNonNull(dependecies);
83 public static <T> @NonNull GeneratorResult<T> of(final Unloaded<T> result) {
84 return new GeneratorResult<>(result, ImmutableSet.of());
87 public static <T> @NonNull GeneratorResult<T> of(final Unloaded<T> result,
88 final Collection<Class<?>> dependencies) {
89 return dependencies.isEmpty() ? of(result) : new GeneratorResult<>(result,
90 ImmutableSet.copyOf(dependencies));
93 @NonNull Unloaded<T> getResult() {
97 @NonNull ImmutableSet<Class<?>> getDependencies() {
102 private static final ClassLoadingStrategy<CodecClassLoader> STRATEGY = (classLoader, types) -> {
103 verify(types.size() == 1, "Unexpected multiple types", types);
104 final Entry<TypeDescription, byte[]> entry = types.entrySet().iterator().next();
105 return ImmutableMap.of(entry.getKey(), classLoader.loadClass(entry.getKey().getName(), entry.getValue()));
109 verify(ClassLoader.registerAsParallelCapable());
112 private static final Logger LOG = LoggerFactory.getLogger(CodecClassLoader.class);
113 private static final File BYTECODE_DIRECTORY;
116 final String dir = System.getProperty("org.opendaylight.mdsal.binding.dom.codec.loader.bytecodeDumpDirectory");
117 BYTECODE_DIRECTORY = Strings.isNullOrEmpty(dir) ? null : new File(dir);
120 CodecClassLoader(final ClassLoader parentLoader) {
125 * Instantiate a new CodecClassLoader, which serves as the root of generated code loading.
127 * @return A new CodecClassLoader.
129 public static @NonNull CodecClassLoader create() {
130 return AccessController.doPrivileged((PrivilegedAction<CodecClassLoader>)() -> new RootCodecClassLoader());
134 * The name of the target class is formed through concatenation of the name of a {@code bindingInterface} and
135 * specified {@code suffix}.
137 * @param bindingInterface Binding compile-time-generated interface
138 * @param suffix Suffix to use
139 * @param generator Code generator to run
140 * @return A generated class object
141 * @throws NullPointerException if any argument is null
143 public final <T> Class<T> generateClass(final Class<?> bindingInterface,
144 final String suffix, final ClassGenerator<T> generator) {
145 return findClassLoader(requireNonNull(bindingInterface)).doGenerateClass(bindingInterface, suffix, generator);
149 * Append specified loaders to this class loader for the purposes of looking up generated classes. Note that the
150 * loaders are expected to have required classes already loaded. This is required to support generation of
151 * inter-dependent structures, such as those used for streaming binding interfaces.
153 * @param newLoaders Loaders to append
154 * @throws NullPointerException if {@code loaders} is null
156 abstract void appendLoaders(@NonNull Set<LeafCodecClassLoader> newLoaders);
159 * Find the loader responsible for holding classes related to a binding class.
161 * @param bindingClass Class to locate
162 * @return a Loader instance
163 * @throws NullPointerException if {@code bindingClass} is null
165 abstract @NonNull CodecClassLoader findClassLoader(@NonNull Class<?> bindingClass);
167 private <T> Class<T> doGenerateClass(final Class<?> bindingInterface, final String suffix,
168 final ClassGenerator<T> generator) {
169 final String fqcn = bindingInterface.getName() + "$$$" + suffix;
171 synchronized (getClassLoadingLock(fqcn)) {
172 // Attempt to find a loaded class
173 final Class<?> existing = findLoadedClass(fqcn);
174 if (existing != null) {
175 return (Class<T>) existing;
178 final GeneratorResult<T> result = generator.generateClass(this, fqcn, bindingInterface);
179 final Unloaded<T> unloaded = result.getResult();
180 verify(fqcn.equals(unloaded.getTypeDescription().getName()), "Unexpected class in %s", unloaded);
181 verify(unloaded.getAuxiliaryTypes().isEmpty(), "Auxiliary types present in %s", unloaded);
182 dumpBytecode(unloaded);
184 processDependencies(result.getDependencies());
185 return generator.customizeLoading(() -> (Class<T>) unloaded.load(this, STRATEGY).getLoaded());
189 final Class<?> loadClass(final String fqcn, final byte[] byteCode) {
190 synchronized (getClassLoadingLock(fqcn)) {
191 final Class<?> existing = findLoadedClass(fqcn);
192 verify(existing == null, "Attempted to load existing %s", existing);
193 return defineClass(fqcn, byteCode, 0, byteCode.length);
197 private void processDependencies(final Collection<Class<?>> deps) {
198 final Set<LeafCodecClassLoader> depLoaders = new HashSet<>();
199 for (Class<?> dep : deps) {
200 final ClassLoader depLoader = dep.getClassLoader();
201 verify(depLoader instanceof CodecClassLoader, "Dependency %s is not a generated class", dep);
202 if (this.equals(depLoader)) {
208 loadClass(dep.getName());
209 } catch (ClassNotFoundException e) {
210 LOG.debug("Cannot find {} in local loader, attempting to compensate", dep, e);
211 // Root loader is always visible from a leaf, hence the dependency can only be a leaf
212 verify(depLoader instanceof LeafCodecClassLoader, "Dependency loader %s is not a leaf", depLoader);
213 depLoaders.add((LeafCodecClassLoader) depLoader);
217 if (!depLoaders.isEmpty()) {
218 appendLoaders(depLoaders);
222 private static void dumpBytecode(final Unloaded<?> unloaded) {
223 if (BYTECODE_DIRECTORY != null) {
225 unloaded.saveIn(BYTECODE_DIRECTORY);
226 } catch (IOException | IllegalArgumentException e) {
227 LOG.info("Failed to save {}", unloaded.getTypeDescription().getName(), e);