/* * 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 INSTANCES = new WeakHashMap<>(); private final Map 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> 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 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()); } }