Package com.inspiresoftware.lib.dto.geda.assembler.extension.impl

Source Code of com.inspiresoftware.lib.dto.geda.assembler.extension.impl.AbstractMethodSynthesizer$ArgumentTypeContext

/*
* This code is distributed under The GNU Lesser General Public License (LGPLv3)
* Please visit GNU site for LGPLv3 http://www.gnu.org/copyleft/lesser.html
*
* Copyright Denis Pavlov 2009
* Web: http://www.genericdtoassembler.org
* SVN: https://svn.code.sf.net/p/geda-genericdto/code/trunk/
* SVN (mirror): http://geda-genericdto.googlecode.com/svn/trunk/
*/

package com.inspiresoftware.lib.dto.geda.assembler.extension.impl;

import com.inspiresoftware.lib.dto.geda.assembler.SynthesizerUtils;
import com.inspiresoftware.lib.dto.geda.assembler.extension.Cache;
import com.inspiresoftware.lib.dto.geda.assembler.extension.DataReader;
import com.inspiresoftware.lib.dto.geda.assembler.extension.DataWriter;
import com.inspiresoftware.lib.dto.geda.assembler.extension.MethodSynthesizer;
import com.inspiresoftware.lib.dto.geda.exception.GeDAException;
import com.inspiresoftware.lib.dto.geda.exception.GeDARuntimeException;
import com.inspiresoftware.lib.dto.geda.exception.InspectionPropertyNotFoundException;
import com.inspiresoftware.lib.dto.geda.exception.UnableToCreateInstanceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
* Template for method synthesizer.
*
* @author DPavlov
* @since 1.1.2
*/
public abstract class AbstractMethodSynthesizer extends SynthesizerUtils implements MethodSynthesizer {
 
  private static final Logger LOG = LoggerFactory.getLogger(AbstractMethodSynthesizer.class);
 
  private final Lock readLock = new ReentrantLock();
  private final Lock writeLock = new ReentrantLock();
  private static final int MAX_COMPILE_TRIES = 3;

    private Reference<ClassLoader> loader;

 
    /**
     * The <code>int</code> value representing the <code>public</code>
     * modifier.
     * @see java.lang.reflect.Modifier
     */   
    private static final int PUBLIC           = 0x00000001;
 
  /**
   * This method allows to "catch" generated classes by class generators such as Javassist or CGLib.
   * The problem with those classes is that some of them are loaded by private ClassLoaders and are
   * invisible to other generated classes such as DataReaders and DataWriters. The basic "catch" is
   * to track $ sign in the class name that is put by both of those generators.
   *
   * Typically this problem arrises with lazy initialized fields via proxies in sub entities.
   *
   * @param method class method
   * @return class that declares the method
   */
  public static Class< ? > getValidDeclaringClass(final java.lang.reflect.Method method) {
    final Class< ? > decl = method.getDeclaringClass();
    if (decl.isAnonymousClass() || decl.getName().indexOf('$') == -1) {
      return decl;
    }
    final Class< ? > declS = decl.getSuperclass();
    if (declS.equals(Object.class)) {
      final Class< ? >[] declIs = decl.getInterfaces();
      for (int i = 0; i < declIs.length; i++) {
        if (declaringClassIsValid(declIs[i], method)) {
          return declIs[i];
        }
      }
      throw new GeDARuntimeException("Unable to identify interface for proxy object");
    }
    if (declaringClassIsValid(declS, method)) {
      return declS;
    }
    final Class< ? >[] declIs = decl.getInterfaces();
    for (int i = 0; i < declIs.length; i++) {
      if (declaringClassIsValid(declIs[i], method)) {
        return declIs[i];
      }
    }
    throw new GeDARuntimeException("Unable to identify interface for proxy object");
  }
 
  private static boolean declaringClassIsValid(final Class< ? > clazz, final java.lang.reflect.Method method) {
    try {
      clazz.getMethod(method.getName(), method.getParameterTypes());
      return true;
    } catch (Exception all) {
      return false;
    }
  }

 
  /** DataReaders instances cache. */
  private static final Cache<Object> READER_CACHE = new SoftReferenceCache<Object>();
  /** DataWriters instances cache. */
  private static final Cache<Object> WRITER_CACHE = new SoftReferenceCache<Object>();
 
  /**
   * Primitive to wrapper conversion map.
   */
  protected static final Map<String, String> PRIMITIVE_TO_WRAPPER = new HashMap<String, String>();
  static {
    PRIMITIVE_TO_WRAPPER.put("byte",   Byte.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("short",   Short.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("int",   Integer.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("long",   Long.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("float",   Float.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("double",   Double.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("boolean", Boolean.class.getCanonicalName());
    PRIMITIVE_TO_WRAPPER.put("char",   Character.class.getCanonicalName());
  }
 
  /**
   * Primitive to wrapper conversion map.
   */
  protected static final Map<String, Class< ? >> PRIMITIVE_TO_WRAPPER_CLASS = new HashMap<String,  Class< ? >>();
  static {
    PRIMITIVE_TO_WRAPPER_CLASS.put("byte",     Byte.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("short",   Short.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("int",     Integer.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("long",     Long.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("float",   Float.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("double",   Double.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("boolean",   Boolean.class);
    PRIMITIVE_TO_WRAPPER_CLASS.put("char",     Character.class);
  }

  /**
   * Wrapper to promitive conversion map.
   */
  protected static final Map<String, String> WRAPPER_TO_PRIMITIVE = new HashMap<String, String>();
  static {
    WRAPPER_TO_PRIMITIVE.put("byte",   ".byteValue()");
    WRAPPER_TO_PRIMITIVE.put("short",   ".shortValue()");
    WRAPPER_TO_PRIMITIVE.put("int",   ".intValue()");
    WRAPPER_TO_PRIMITIVE.put("long",   ".longValue()");
    WRAPPER_TO_PRIMITIVE.put("float",   ".floatValue()");
    WRAPPER_TO_PRIMITIVE.put("double",   ".doubleValue()");
    WRAPPER_TO_PRIMITIVE.put("boolean", ".booleanValue()");
    WRAPPER_TO_PRIMITIVE.put("char",   ".charValue()");
  }

 
  /**
   * Default constructor synthesizers. This set a template for auto
     * class generation. It is a requirement to provide a valid class loader
     * to which files will be loaded. This in effect like a visitor pattern
     * whereby we generate required file and enhance the current class
     * loader by providing new class. The reason for this is that newly
     * generated files need access to graph of objects available to class
     * loader. Also it allows us to separate our contributions to each class
     * loader.
     *
     * @param classLoader class loader that will be used to load generated classes
   */
  public AbstractMethodSynthesizer(final ClassLoader classLoader) {
    super();
        this.loader = initialiseClassLoaderWeakReference(classLoader);
  }

    /**
     * Hook for sub classes to assign correct class loader reference.
     * We must maintain only weak reference to the class loader.
     *
     * @param classLoader class loader to contribute generated files to.
     */
    protected Reference<ClassLoader> initialiseClassLoaderWeakReference(final ClassLoader classLoader) {
        return new SoftReference<ClassLoader>(classLoader);
    }

    /**
     * Hook for changing class loader reference.
     * Invokes #initialiseClassLoaderWeakReference(getClassLoader()).
     */
    protected final void enhanceClassLoader() {
        this.loader = initialiseClassLoaderWeakReference(getClassLoader());
    }
 
 
  /**
   * @param configuration configuration name
   * @param value value to set
   * @return true if configuration was set, false if not set or invalid
   * @throws GeDAException any exceptions during configuration
   */
  public boolean configure(final String configuration, final Object value) throws GeDAException {
    return false;
  }

  /*
   * @param cleanUpReaderCycle reader cache clean up cycle
   */
  private boolean setCleanUpReaderCycle(final Object cleanUpReaderCycle) throws GeDAException {
    return READER_CACHE.configure("cleanUpCycle", cleanUpReaderCycle);
  }

  /*
   * @param cleanUpWriterCycle writer cache clean up cycle
   */
  private boolean setCleanUpWriterCycle(final Object cleanUpWriterCycle) throws GeDAException {
    return WRITER_CACHE.configure("cleanUpCycle", cleanUpWriterCycle);
  }
 
  /**
   * Perform reader validation.
   *
   * @param descriptor descriptor
   * @throws InspectionPropertyNotFoundException property not found
   * @throws GeDARuntimeException any abnormality
   */
  protected void preMakeReaderValidation(final PropertyDescriptor descriptor)
      throws InspectionPropertyNotFoundException, GeDARuntimeException {
    final Method readMethod = descriptor.getReadMethod();
        if (readMethod == null) {
            throw new InspectionPropertyNotFoundException("No read method for: ", descriptor.getName());
        }
    final Class< ? > target = getValidDeclaringClass(readMethod);
    if ((target.getModifiers() & PUBLIC) == 0) {
      throw new GeDARuntimeException(target.getCanonicalName()
          + " does not have [public] modifier. This will cause IllegalAccessError during runtime.");
    }
  }
 
  /**
   * Simple object to hold plain text representation of return type of reader.
   *
   * @author denispavlov
   *
   */
  protected static class ReturnTypeContext {
   
    private final Class< ? > clazz;
    private final String methodReturnType;
    private final String methodReturnTypePrimitiveName;

    /**
     * @param clazz class
     * @param methodReturnType class of object that represent return type
     * @param methodReturnTypePrimitiveName primitive name (or null if this is not primitive)
     */
    public ReturnTypeContext(
        final Class< ? > clazz,
        final String methodReturnType,
        final String methodReturnTypePrimitiveName) {
      this.clazz = clazz;
      this.methodReturnType = methodReturnType;
      this.methodReturnTypePrimitiveName = methodReturnTypePrimitiveName;
    }
   
    /**
     * @return raw class
     */
    public Class< ? > getClazz() {
      return clazz;
    }

    /**
     * @return class of object that represent return type
     */
    public String getMethodReturnType() {
      return methodReturnType;
    }

    /**
     * @return primitive name (or null if this is not primitive)
     */
    public String getMethodReturnTypePrimitiveName() {
      return methodReturnTypePrimitiveName;
    }
   
    /**
     * @return true if this is a primitive type
     */
    public boolean isPrimitive() {
      return methodReturnTypePrimitiveName != null;
    }
   
  }
 
  /**
   * @param readerClassName class name
   * @param sourceClassGetterMethodReturnType return type
   * @return context
   * @throws GeDARuntimeException if unable to determine correct return type
   */
  protected final ReturnTypeContext getReturnTypeContext(final String readerClassName, final Type sourceClassGetterMethodReturnType)
      throws GeDARuntimeException {

        try {
            final Class rcl = getClassForType(sourceClassGetterMethodReturnType);
            if (rcl.isPrimitive()) {
                return new ReturnTypeContext(rcl,
                        PRIMITIVE_TO_WRAPPER.get(rcl.getCanonicalName()), rcl.getCanonicalName());
            }
            return new ReturnTypeContext(rcl, rcl.getCanonicalName(), null);
        } catch (GeDARuntimeException gre) {
            throw new GeDARuntimeException("Unable to determine correct return type from getter method in class: " + readerClassName, gre);
        }
  }
 
  /** {@inheritDoc} */
  public final DataReader synthesizeReader(final PropertyDescriptor descriptor)
    throws InspectionPropertyNotFoundException, UnableToCreateInstanceException, GeDARuntimeException {

    preMakeReaderValidation(descriptor);
   
    final Method readMethod = descriptor.getReadMethod();
    final String sourceClassNameFull = getValidDeclaringClass(readMethod).getCanonicalName();
    final String sourceClassGetterMethodName = readMethod.getName();
   
    final String readerClassName = generateClassName("DataReader", sourceClassNameFull, sourceClassGetterMethodName);
   
    DataReader reader;
   
    reader = getFromCacheOrCreateFromClassLoader(readerClassName, READER_CACHE, getClassLoader());
   
    if (reader == null) {
      readLock.lock();
      final MakeContext ctx = new MakeContext(DataReader.class.getCanonicalName());
      try {
        do {
          reader = makeReaderClass(getClassLoader(), readMethod,
              readerClassName, sourceClassNameFull, sourceClassGetterMethodName, readMethod.getGenericReturnType(), ctx);
          if (reader == null) {
            reader = getFromCacheOrCreateFromClassLoader(readerClassName, READER_CACHE, getClassLoader());
          } else {
            READER_CACHE.put(readerClassName.hashCode(), reader);
          }
        } while (reader == null);
      } finally {
        readLock.unlock();
      }
    }
    return reader;
  }

  @SuppressWarnings("unchecked")
  private <T> T getFromCacheOrCreateFromClassLoader(final String className,
                            final Cache<Object> cache,
                            final ClassLoader classLoader)
      throws UnableToCreateInstanceException {
    Object instance;

    instance = cache.get(className.hashCode());
    if (instance != null) {
      return (T) instance;
    }
   
    instance = createInstanceFromClassLoader(classLoader, className);
    if (instance != null) {
      return (T) instance;
    }
    return null;
  }

  /**
   * Method to be overridden by specific synthesizer - contains the low level code
   * to actually generating Class object.
   *
   * @param loader class loader
   * @param readMethod read method object
   * @param readerClassName name of the reader class
   * @param sourceClassNameFull name of the class of source object (i.e. whose getter will be invoked)
   * @param sourceClassGetterMethodName name of the getter method to be invoked on the source object
   * @param sourceClassGetterMethodReturnType class name of the return type to be returned
   * @param ctx compilation context. Need to invoke .next() for every unsuccessful compilation attempt.
   *
   * @return data reader instance.
   *
   * @throws UnableToCreateInstanceException whenever there is a problem creating an instance of the generated class
   * @throws GeDARuntimeException any exceptions during class generation
   */
  protected abstract DataReader makeReaderClass(
      final ClassLoader loader,
      final Method readMethod,
      final String readerClassName,
      final String sourceClassNameFull,
      final String sourceClassGetterMethodName,
      final Type sourceClassGetterMethodReturnType,
      final MakeContext ctx) throws UnableToCreateInstanceException, GeDARuntimeException;

 
  /**
   * Perform writer validation.
   *
   * @param descriptor descriptor
   * @throws InspectionPropertyNotFoundException property not found
   * @throws GeDARuntimeException any abnormality
   */
  protected void preMakeWriterValidation(final PropertyDescriptor descriptor)
      throws InspectionPropertyNotFoundException, GeDARuntimeException {
    final Method writeMethod = descriptor.getWriteMethod();
        if (writeMethod == null) {
            throw new InspectionPropertyNotFoundException("No write method for: ", descriptor.getName());
        }
    final Class< ? > target = getValidDeclaringClass(writeMethod);
    if ((target.getModifiers() & PUBLIC) == 0) {
      throw new GeDARuntimeException(target.getCanonicalName()
          + " does not have [public] modifier. This will cause IllegalAccessError during runtime.");
    }

  }
 
 
  /**
   * Simple object to hold plain text representation of argument type of reader.
   *
   * @author denispavlov
   *
   */
  protected class ArgumentTypeContext {

    private final Class< ? > clazz;
    private final String methodArgType;
    private final String methodArgPrimitiveName;
   
    /**
     * @param clazz class
     * @param methodArgType object class name
     * @param methodArgPrimitiveName primitive name (or null if this type is not primitive)
     */
    public ArgumentTypeContext(
        final Class< ? > clazz,
        final String methodArgType,
        final String methodArgPrimitiveName) {
      this.clazz = clazz;
      this.methodArgType = methodArgType;
      this.methodArgPrimitiveName = methodArgPrimitiveName;
    }
   
    /**
     * @return raw class
     */
    public Class< ? > getClazz() {
      return clazz;
    }
   
    /**
     * @return object class name
     */
    public String getMethodArgType() {
      return methodArgType;
    }
   
    /**
     * @return primitive name (or null if this type is not primitive)
     */
    public String getMethodArgPrimitiveName() {
      return methodArgPrimitiveName;
    }
   
    /**
     * @return true if this is a primitive type
     */
    public boolean isPrimitive() {
      return methodArgPrimitiveName != null;
    }
   
  }
 
  /**
   * @param sourceClassSetterMethodArgumentClass class name of the argument type passed to setter
   * @return context
   */
  protected final ArgumentTypeContext getArgumentTypeContext(final Class< ? > sourceClassSetterMethodArgumentClass) {

    if (sourceClassSetterMethodArgumentClass.isPrimitive()) {
      return new ArgumentTypeContext(sourceClassSetterMethodArgumentClass,
          PRIMITIVE_TO_WRAPPER.get(sourceClassSetterMethodArgumentClass.getCanonicalName()),
          sourceClassSetterMethodArgumentClass.getCanonicalName());
    }
    return new ArgumentTypeContext(sourceClassSetterMethodArgumentClass, sourceClassSetterMethodArgumentClass.getCanonicalName(), null);

  }
 
  /** {@inheritDoc} */
  public final DataWriter synthesizeWriter(final PropertyDescriptor descriptor)
      throws InspectionPropertyNotFoundException, UnableToCreateInstanceException, GeDARuntimeException {
   
    preMakeWriterValidation(descriptor);
   
    final Method writeMethod = descriptor.getWriteMethod();
    final String classNameFull = getValidDeclaringClass(writeMethod).getCanonicalName();
    final String methodName = writeMethod.getName();
   
    final String writerClassName = generateClassName("DataWriter", classNameFull, methodName);
   
   
    DataWriter writer;

    writer = getFromCacheOrCreateFromClassLoader(writerClassName, WRITER_CACHE, getClassLoader());
    if (writer == null) {
      writeLock.lock();
      final MakeContext ctx = new MakeContext(DataWriter.class.getCanonicalName());
      try {
        do {
          writer = makeWriterClass(getClassLoader(), writeMethod,
              writerClassName, classNameFull, methodName, writeMethod.getParameterTypes()[0], ctx);
          if (writer == null) {
            writer = getFromCacheOrCreateFromClassLoader(writerClassName, WRITER_CACHE, getClassLoader());
          } else {
            WRITER_CACHE.put(writerClassName.hashCode(), writer);
          }
        } while (writer == null);
      } finally {
        writeLock.unlock();
      }
    }
    return writer;
  }

  /**
   * Method to be overridden by specific synthesizer - contains the low level code
   * to actually generating Class object.
   *
   * @param loader class loader
   * @param writeMethod write method object
   * @param writerClassName name of the reader class
   * @param sourceClassNameFull name of the class of source object (i.e. whose setter will be invoked)
   * @param sourceClassSetterMethodName  name of the setter method to be invoked on the source object
   * @param sourceClassSetterMethodArgumentClass  class name of the argument type passed to setter
   * @param ctx  compilation context. Need to invoke .next() for every unsuccessful compilation attempt.
   *
   * @return DataWriter instance
   *
   * @throws UnableToCreateInstanceException  whenever there is a problem creating an instance of the generated class
   */
  protected abstract DataWriter makeWriterClass(
      final ClassLoader loader,
      final Method writeMethod,
      final String writerClassName,
      final String sourceClassNameFull,
      final String sourceClassSetterMethodName,
      final Class< ? > sourceClassSetterMethodArgumentClass,
      final MakeContext ctx) throws UnableToCreateInstanceException;
 
  /**
   * Class loader reference.
   */
  protected ClassLoader getClassLoader() {
        ClassLoader cl = loader.get();
        if (cl == null) { // Cl was garbage collected - something gone really wrong
            throw new GeDARuntimeException("Class loader has been gc'ed");
        }
    return cl;
  }
 
  @SuppressWarnings("unchecked")
  private <T> T createInstanceFromClassLoader(final ClassLoader cl, final String clazzName)
      throws UnableToCreateInstanceException {
    try {
      final Class< ? > clazz = Class.forName(clazzName, true, cl);
      return (T) clazz.newInstance();
    } catch (ClassNotFoundException cnfe) {
      // That's OK we don't have it
      return null;
    } catch (Throwable exp) {
      throw new UnableToCreateInstanceException(clazzName, "Unable to create instance of: " + clazzName, exp);
    }
  }
 
  private String generateClassName(final String prefix, final String declaringClass, final String methodName) {
    return declaringClass + prefix + "M" + methodName + "ID" + getSynthesizerId();
  }
 
  /**
   * @return synthesizer id that will be used in class name
   */
  protected abstract String getSynthesizerId();
     
  /**
   * Inner class to keep track of recursive attempts to compile a class.
   *
   * @author denispavlov
   *
   */
  public static final class MakeContext {
    private int tryNo;
    private final String classType;
   
    /**
     * @param classType class type (reader/writer)
     */
    public MakeContext(final String classType) {
      this.classType = classType;
      this.tryNo = 0;
    }
   
    /**
     * To be used to attempt recovery through classloader instance creation.
     * If next counter exceed the number of tries and exception is thrown,
     * otherwise another cycle is attempted.
     *
     * @param exp exception which occured whilst auto generating a class
     * @param source source code.
     * @throws UnableToCreateInstanceException thrown after number of tries is exceeded.
     *         wraps the original exception.
     */
    public void next(final Exception exp, final String source) throws UnableToCreateInstanceException {
      this.tryNo++;
      if (this.tryNo > MAX_COMPILE_TRIES) {
        throw new UnableToCreateInstanceException(classType,
            "Unable to create class type [" + classType + "]\n"
            + "with source:\n============>" + source + "\n<=============", exp);
      }
    }
  }

    /** {@inheritDoc} */
    public void releaseResources() {
        loader.clear();
    }

}
TOP

Related Classes of com.inspiresoftware.lib.dto.geda.assembler.extension.impl.AbstractMethodSynthesizer$ArgumentTypeContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.