--- /dev/null
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.javav2.generator.impl.util.javassist;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import java.util.Collection;
+import java.util.Map;
+import java.util.WeakHashMap;
+import javassist.CannotCompileException;
+import javassist.ClassClassPath;
+import javassist.ClassPath;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.CtMethod;
+import javassist.LoaderClassPath;
+import javassist.Modifier;
+import javassist.NotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Users of this utility class are expected to synchronize on this instance it
+ * they need to ensure atomic operations on it.
+ */
+@Beta
+public final class JavassistUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JavassistUtils.class);
+
+ private static final Map<ClassPool, JavassistUtils> INSTANCES = new WeakHashMap<>();
+ private final Map<ClassLoader, ClassPath> loaderClassPaths = new WeakHashMap<>();
+ private final ClassPool classPool;
+
+ private JavassistUtils(final ClassPool pool) {
+ classPool = Preconditions.checkNotNull(pool);
+ }
+
+ /**
+ * Get a utility instance for a particular class pool. A new instance is
+ * created if this is a new pool. If an instance already exists, is is
+ * returned.
+ *
+ * @param pool
+ * - backing class pool
+ * @return shared utility instance for specified pool
+ */
+ public static synchronized JavassistUtils forClassPool(final ClassPool pool) {
+ JavassistUtils ret = INSTANCES.get(Preconditions.checkNotNull(pool));
+ if (ret == null) {
+ ret = new JavassistUtils(pool);
+ INSTANCES.put(pool, ret);
+ }
+ return ret;
+ }
+
+ /**
+ * Generate and add method to class.
+ *
+ * @param baseClass
+ * - class for adding method
+ * @param methodReturnType
+ * - return type of method
+ * @param methodName
+ * - name of method
+ * @param methodParameter
+ * - parameter of method
+ * @param methodGenerator
+ * - method generator
+ * @throws CannotCompileException
+ */
+ public void method(final CtClass baseClass, final Class<?> methodReturnType, final String methodName,
+ final Class<?> methodParameter, final MethodGenerator methodGenerator) throws CannotCompileException {
+ final CtClass[] pa = new CtClass[] { asCtClass(methodParameter) };
+ final CtMethod method = new CtMethod(asCtClass(methodReturnType), methodName, pa, baseClass);
+
+ methodGenerator.process(method);
+ baseClass.addMethod(method);
+ }
+
+ /**
+ * Generate and add method to class.
+ *
+ * @param baseClass
+ * - class for adding method
+ * @param methodReturnType
+ * - return type of method
+ * @param methodName
+ * - name of method
+ * @param methodParameters
+ * - parameters of method
+ * @param methodGenerator
+ * - method generator
+ * @throws CannotCompileException
+ */
+ public void method(final CtClass baseClass, final Class<?> methodReturnType, final String methodName,
+ final Collection<? extends Class<?>> methodParameters, final MethodGenerator methodGenerator)
+ throws CannotCompileException {
+ final CtClass[] pa = new CtClass[methodParameters.size()];
+
+ int i = 0;
+ for (final Class<?> parameter : methodParameters) {
+ pa[i] = asCtClass(parameter);
+ ++i;
+ }
+
+ final CtMethod method = new CtMethod(asCtClass(methodReturnType), methodName, pa, baseClass);
+ methodGenerator.process(method);
+ baseClass.addMethod(method);
+ }
+
+ /**
+ * Generate and add static method to class.
+ *
+ * @param baseClass
+ * - class for adding method
+ * @param methodReturnType
+ * - return type of method
+ * @param methodName
+ * - name of method
+ * @param methodParameter
+ * - parameter of method
+ * @param methodGenerator
+ * - method generator
+ * @throws CannotCompileException
+ */
+ public void staticMethod(final CtClass baseClass, final Class<?> methodReturnType, final String methodName,
+ final Class<?> methodParameter, final MethodGenerator methodGenerator) throws CannotCompileException {
+ final CtClass[] pa = new CtClass[] { asCtClass(methodParameter) };
+ final CtMethod method = new CtMethod(asCtClass(methodReturnType), methodName, pa, baseClass);
+ methodGenerator.process(method);
+ baseClass.addMethod(method);
+ }
+
+ /**
+ * Implement methods to class from other class.
+ *
+ * @param target
+ * - class for implementing methods
+ * @param source
+ * - source class of methods to be implemented in target
+ * @param methodGenerator
+ * - method generator
+ * @throws CannotCompileException
+ */
+ public void implementMethodsFrom(final CtClass target, final CtClass source, final MethodGenerator methodGenerator)
+ throws CannotCompileException {
+ for (final CtMethod method : source.getMethods()) {
+ if (method.getDeclaringClass() == source) {
+ final CtMethod redeclaredMethod = new CtMethod(method, target, null);
+ methodGenerator.process(redeclaredMethod);
+ target.addMethod(redeclaredMethod);
+ }
+ }
+ }
+
+ /**
+ * Generate and add class to global class pool.
+ *
+ * @param className
+ * - name of class
+ * @param classGenerator
+ * - class generator
+ * @return generated class
+ * @throws CannotCompileException
+ */
+ public CtClass createClass(final String className, final ClassGenerator classGenerator) throws CannotCompileException {
+ final CtClass target = classPool.makeClass(className);
+ classGenerator.process(target);
+ return target;
+ }
+
+ /**
+ * Generate and add class to global class pool with implemented interface.
+ *
+ * @param className
+ * - name of class
+ * @param superInterface
+ * - interface to be implemented to the class
+ * @param classGenerator
+ * - class generator
+ * @return generated class with interface
+ * @throws CannotCompileException
+ */
+ public CtClass createClass(final String className, final CtClass superInterface, final ClassGenerator classGenerator)
+ throws CannotCompileException {
+ final CtClass target = classPool.makeClass(className);
+ implementsType(target, superInterface);
+ classGenerator.process(target);
+ return target;
+ }
+
+ /**
+ * Instantiate a new class based on a prototype. The class is set to
+ * automatically prune.
+ *
+ * @param prototype
+ * - prototype class fully qualified name
+ * @param target
+ * - target class fully qualified name
+ * @param customizer
+ * - customization callback to be invoked on the new class
+ * @return An instance of the new class
+ * @throws NotFoundException
+ * - when the prototype class is not found
+ */
+ public synchronized CtClass instantiatePrototype(final String prototype, final String target,
+ final ClassCustomizer customizer) throws NotFoundException {
+ final CtClass result = classPool.getAndRename(prototype, target);
+
+ try {
+ customizer.customizeClass(result);
+ } catch (final Exception e) {
+ LOG.warn("Failed to customize {} from prototype {}", target, prototype, e);
+ result.detach();
+ throw new IllegalStateException(String.format("Failed to instantiate prototype %s as %s", prototype, target),
+ e);
+ }
+
+ result.stopPruning(false);
+ return result;
+ }
+
+ /**
+ * Implements type to class.
+ *
+ * @param baseClass
+ * - class for implements interface
+ * @param superInterface
+ * - interface to be implemented
+ */
+ public void implementsType(final CtClass baseClass, final CtClass superInterface) {
+ Preconditions.checkArgument(superInterface.isInterface(), "Supertype must be interface");
+ baseClass.addInterface(superInterface);
+ }
+
+ /**
+ * Get class from class pool.
+ *
+ * @param class1
+ * - class for getting from class pool
+ * @return class
+ */
+ public CtClass asCtClass(final Class<?> class1) {
+ return get(this.classPool, class1);
+ }
+
+ /**
+ * Create and add field to class.
+ *
+ * @param baseClass
+ * - class for adding field
+ * @param fieldName
+ * - name of field
+ * @param fieldType
+ * - type of field
+ * @return class of field
+ * @throws CannotCompileException
+ */
+ public CtField field(final CtClass baseClass, final String fieldName, final Class<?> fieldType)
+ throws CannotCompileException {
+ final CtField field = new CtField(asCtClass(fieldType), fieldName, baseClass);
+ field.setModifiers(Modifier.PUBLIC);
+ baseClass.addField(field);
+ return field;
+ }
+
+ /**
+ * Create and add static field to class.
+ *
+ * @param baseClass
+ * - class for adding field
+ * @param fieldName
+ * - name of field
+ * @param fieldType
+ * - type of field
+ * @return class of field
+ * @throws CannotCompileException
+ */
+ public CtField staticField(final CtClass baseClass, final String fieldName, final Class<?> fieldType)
+ throws CannotCompileException {
+ return staticField(baseClass, fieldName, fieldType, null);
+ }
+
+ /**
+ * Create and add static field to class.
+ *
+ * @param baseClass
+ * - class for adding field
+ * @param fieldName
+ * - name of field
+ * @param fieldType
+ * - type of field
+ * @param sourceGenerator
+ * - source generator
+ * @return class of field
+ * @throws CannotCompileException
+ */
+ public CtField staticField(final CtClass baseClass, final String fieldName, final Class<?> fieldType,
+ final SourceCodeGenerator sourceGenerator) throws CannotCompileException {
+ final CtField field = new CtField(asCtClass(fieldType), fieldName, baseClass);
+ field.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
+ baseClass.addField(field);
+
+ if (sourceGenerator != null) {
+ sourceGenerator.appendField(field, null);
+ }
+
+ return field;
+ }
+
+ /**
+ * Get class from pool.
+ *
+ * @param pool
+ * - class pool
+ * @param clazz
+ * - search class in class pool
+ * @return class if exists
+ */
+ public CtClass get(final ClassPool pool, final Class<? extends Object> clazz) {
+ try {
+ return pool.get(clazz.getName());
+ } catch (final NotFoundException nfe1) {
+ appendClassLoaderIfMissing(clazz.getClassLoader());
+ try {
+ return pool.get(clazz.getName());
+ } catch (final NotFoundException nfe2) {
+ LOG.warn("Appending ClassClassPath for {}", clazz, nfe2);
+ pool.appendClassPath(new ClassClassPath(clazz));
+ try {
+ return pool.get(clazz.getName());
+ } catch (final NotFoundException e) {
+ LOG.warn("Failed to load class {} from pool {}", clazz, pool, e);
+ throw new IllegalStateException("Failed to load class", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Append class to class pool if doesn't exist.
+ *
+ * @param loader
+ * - class loader of search class
+ */
+ public synchronized void appendClassLoaderIfMissing(final ClassLoader loader) {
+ if (!loaderClassPaths.containsKey(loader)) {
+ final ClassPath ctLoader = new LoaderClassPath(loader);
+ classPool.appendClassPath(ctLoader);
+ loaderClassPaths.put(loader, ctLoader);
+ }
+ }
+
+ /**
+ * Ensure if is class in class loader.
+ *
+ * @param child
+ * - search class
+ */
+ public void ensureClassLoader(final Class<?> child) {
+ appendClassLoaderIfMissing(child.getClassLoader());
+ }
+}