/* * 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.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.UnableToCreateInstanceException; import org.apache.bcel.Constants; import org.apache.bcel.generic.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map; import static org.apache.bcel.Constants.ACC_PUBLIC; import static org.apache.bcel.Constants.ACC_SUPER; /** * CGLib implementation. * * @author DPavlov * @since 1.1.0 */ public class BCELMethodSynthesizer extends AbstractMethodSynthesizer implements MethodSynthesizer, BaseDirectoryProvider { private static final int MAJOR = Constants.MAJOR_1_5; private static final int MINOR = Constants.MINOR_1_5; private static final Logger LOG = LoggerFactory.getLogger(BCELMethodSynthesizer.class); private String baseDir = null; /** * Primitive to wrapper conversion map. */ protected static final Map<String, org.apache.bcel.generic.Type> PRIMITIVE_TO_TYPE = new HashMap<String, org.apache.bcel.generic.Type>(); static { PRIMITIVE_TO_TYPE.put("byte", org.apache.bcel.generic.Type.BYTE); PRIMITIVE_TO_TYPE.put("short", org.apache.bcel.generic.Type.SHORT); PRIMITIVE_TO_TYPE.put("int", org.apache.bcel.generic.Type.INT); PRIMITIVE_TO_TYPE.put("long", org.apache.bcel.generic.Type.LONG); PRIMITIVE_TO_TYPE.put("float", org.apache.bcel.generic.Type.FLOAT); PRIMITIVE_TO_TYPE.put("double", org.apache.bcel.generic.Type.DOUBLE); PRIMITIVE_TO_TYPE.put("boolean", org.apache.bcel.generic.Type.BOOLEAN); PRIMITIVE_TO_TYPE.put("char", org.apache.bcel.generic.Type.CHAR); } /** * Default constructor that adds GeDA path to pool for generating files. * * @param classLoader class loader */ public BCELMethodSynthesizer(ClassLoader classLoader) { super(classLoader); } /** * Manual BCEL synthesizer constructor that enables generated file dumps. * * @param classLoader class loader * @param baseDir base directory */ public BCELMethodSynthesizer(final ClassLoader classLoader, final String baseDir) { this(classLoader); this.configure("baseDir", baseDir); } /** {@inheritDoc} */ protected SoftReference<ClassLoader> initialiseClassLoaderWeakReference(final ClassLoader classLoader) { if (baseDir == null) { return new SoftReference<ClassLoader>(new ByteClassLoader(classLoader)); } return new SoftReference<ClassLoader>(new FileClassLoader(classLoader, this)); } /** {@inheritDoc} */ @Override protected String getSynthesizerId() { return "bcel"; } /** {@inheritDoc} */ public String getBaseDir(final String name) { return baseDir; } /* * Load class as byte array. */ @SuppressWarnings("unchecked") private <T> T loadClass(final ClassLoader loader, final String className, final ClassGen cg) throws IOException, InstantiationException, IllegalAccessException { if (this.baseDir == null) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); cg.getJavaClass().dump(out); final Class< ? > clazzB = ((ByteClassLoader) loader).loadClass(className, out.toByteArray()); return (T) clazzB.newInstance(); } final String readerSimpleName = className.substring(className.lastIndexOf('.') + 1); final File clazz = new File(this.baseDir + readerSimpleName + ".class"); clazz.deleteOnExit(); LOG.debug("Attempt to create source file: {}", clazz.getAbsolutePath()); clazz.createNewFile(); FileOutputStream fos = null; try { fos = new FileOutputStream(clazz); cg.getJavaClass().dump(fos); } finally { if (fos != null) { fos.close(); } } LOG.debug("Successfully created source file: {}", clazz.getAbsolutePath()); try { final Class< ? > clazzF = loader.loadClass(className); return (T) clazzF.newInstance(); } catch (ClassNotFoundException cnfe) { throw new IOException(cnfe.getMessage()); } } private String enhanceInnerClassName(final String clazz, final boolean inner) { if (inner) { final int pos = clazz.lastIndexOf('.'); return clazz.substring(0, pos) + '$' + clazz.substring(pos + 1); } return clazz; } /** {@inheritDoc} */ protected DataReader makeReaderClass( final ClassLoader loader, final java.lang.reflect.Method readMethod, final String readerClassName, final String sourceClassNameFull, final String sourceClassGetterMethodName, final java.lang.reflect.Type sourceClassGetterMethodReturnType, final MakeContext ctx) throws UnableToCreateInstanceException, GeDARuntimeException { try { LOG.debug("Generating DataReader: {}", readerClassName); final ReturnTypeContext rtc = getReturnTypeContext(readerClassName, sourceClassGetterMethodReturnType); final ClassGen cg = new ClassGen(readerClassName, "java.lang.Object", "<generated>", ACC_PUBLIC | ACC_SUPER, new String[] { DataReader.class.getCanonicalName() }); final ConstantPoolGen cp = cg.getConstantPool(); // cg creates constant pool cg.setMajor(MAJOR); cg.setMinor(MINOR); cg.addEmptyConstructor(ACC_PUBLIC); if (LOG.isDebugEnabled()) { LOG.debug("<init> method:\n{}", cg.getMethods()[0].getCode().toString(true)); } final InstructionList il = new InstructionList(); final InstructionFactory factory = new InstructionFactory(cg); // read method final MethodGen read = new MethodGen( ACC_PUBLIC, org.apache.bcel.generic.Type.OBJECT, new org.apache.bcel.generic.Type[] { // argument types org.apache.bcel.generic.Type.OBJECT }, new String[] { "source" }, "read", readerClassName, il, cp); final org.apache.bcel.generic.Type sourceClassType = new ObjectType(sourceClassNameFull); il.append(new ALOAD(1)); il.append(factory.createCast(read.getArgumentType(0), sourceClassType)); final LocalVariableGen clazz = read.addLocalVariable("clazz", sourceClassType, null, null); final int clazzIndex = clazz.getIndex(); clazz.setStart(il.append(new ASTORE(clazzIndex))); il.append(new ALOAD(clazzIndex)); il.append(factory.createInvoke(sourceClassNameFull, sourceClassGetterMethodName, rtc.isPrimitive() ? PRIMITIVE_TO_TYPE.get(rtc.getMethodReturnTypePrimitiveName()) : new ObjectType(enhanceInnerClassName(rtc.getMethodReturnType(), rtc.getClazz().isMemberClass())), org.apache.bcel.generic.Type.NO_ARGS, readMethod.getDeclaringClass().isInterface() ? Constants.INVOKEINTERFACE : Constants.INVOKEVIRTUAL)); if (rtc.isPrimitive()) { il.append(factory.createInvoke(rtc.getMethodReturnType(), "valueOf", new ObjectType(rtc.getMethodReturnType()), new org.apache.bcel.generic.Type[] { // argument types PRIMITIVE_TO_TYPE.get(rtc.getMethodReturnTypePrimitiveName()) }, Constants.INVOKESTATIC)); } clazz.setEnd(il.append(InstructionFactory.ARETURN)); read.setMaxStack(); cg.addMethod(read.getMethod()); if (LOG.isDebugEnabled()) { LOG.debug("read method:\n{}", read.getMethod().getCode().toString(true)); } il.dispose(); // return type method final MethodGen returnType = new MethodGen( ACC_PUBLIC, org.apache.bcel.generic.Type.CLASS, org.apache.bcel.generic.Type.NO_ARGS, null, "getReturnType", readerClassName, il, cp); il.append(new LDC_W(cp.addClass(enhanceInnerClassName(rtc.getMethodReturnType(), rtc.getClazz().isMemberClass())))); il.append(InstructionFactory.ARETURN); returnType.setMaxStack(); cg.addMethod(returnType.getMethod()); if (LOG.isDebugEnabled()) { LOG.debug("return type method:\n{}", returnType.getMethod().getCode().toString(true)); } il.dispose(); return loadClass(loader, readerClassName, cg); } catch (Exception ite) { throw new UnableToCreateInstanceException(readerClassName, "Unable to instantiate class: " + readerClassName, ite); } } /** {@inheritDoc} */ protected DataWriter makeWriterClass( final ClassLoader loader, final java.lang.reflect.Method writeMethod, final String writerClassName, final String sourceClassNameFull, final String sourceClassSetterMethodName, final Class< ? > sourceClassSetterMethodArgumentClass, final MakeContext ctx) throws UnableToCreateInstanceException { try { LOG.error("Generating WriterClass: {}", writerClassName); final ArgumentTypeContext atc = getArgumentTypeContext(sourceClassSetterMethodArgumentClass); final ClassGen cg = new ClassGen(writerClassName, "java.lang.Object", "<generated>", ACC_PUBLIC | ACC_SUPER, new String[] { DataWriter.class.getCanonicalName() }); final ConstantPoolGen cp = cg.getConstantPool(); // cg creates constant pool cg.setMajor(MAJOR); cg.setMinor(MINOR); cg.addEmptyConstructor(ACC_PUBLIC); if (LOG.isDebugEnabled()) { LOG.debug("<init> method:\n{}", cg.getMethods()[0].getCode().toString(true)); } final InstructionList il = new InstructionList(); final InstructionFactory factory = new InstructionFactory(cg); // write method final MethodGen write = new MethodGen( ACC_PUBLIC, org.apache.bcel.generic.Type.VOID, new org.apache.bcel.generic.Type[] { // argument types org.apache.bcel.generic.Type.OBJECT, org.apache.bcel.generic.Type.OBJECT, }, new String[] { "source", "value" }, "write", writerClassName, il, cp); final org.apache.bcel.generic.Type sourceClassType = new ObjectType(sourceClassNameFull); il.append(new ALOAD(1)); // source il.append(factory.createCast(write.getArgumentType(0), sourceClassType)); final LocalVariableGen clazz = write.addLocalVariable("clazz", sourceClassType, null, null); final int clazzIndex = clazz.getIndex(); clazz.setStart(il.append(new ASTORE(clazzIndex))); il.append(new ALOAD(clazzIndex)); il.append(new ALOAD(2)); // value il.append(factory.createCast(write.getArgumentType(1), new ObjectType(enhanceInnerClassName(atc.getMethodArgType(), atc.getClazz().isMemberClass())))); if (atc.isPrimitive()) { final String textMethCall = WRAPPER_TO_PRIMITIVE.get(atc.getMethodArgPrimitiveName()); il.append(factory.createInvoke(atc.getMethodArgType(), textMethCall.substring(1, textMethCall.length() - 2), PRIMITIVE_TO_TYPE.get(atc.getMethodArgPrimitiveName()), org.apache.bcel.generic.Type.NO_ARGS, Constants.INVOKEVIRTUAL)); } il.append(factory.createInvoke(sourceClassNameFull, sourceClassSetterMethodName, org.apache.bcel.generic.Type.VOID, new org.apache.bcel.generic.Type[] { atc.isPrimitive() ? PRIMITIVE_TO_TYPE.get(atc.getMethodArgPrimitiveName()) : new ObjectType(enhanceInnerClassName(atc.getMethodArgType(), atc.getClazz().isMemberClass())) }, writeMethod.getDeclaringClass().isInterface() ? Constants.INVOKEINTERFACE : Constants.INVOKEVIRTUAL)); clazz.setEnd(il.append(InstructionFactory.RETURN)); write.setMaxStack(); cg.addMethod(write.getMethod()); if (LOG.isDebugEnabled()) { LOG.debug("write method:\n{}", write.getMethod().getCode().toString(true)); } il.dispose(); // arg type method final MethodGen paramType = new MethodGen( ACC_PUBLIC, org.apache.bcel.generic.Type.CLASS, org.apache.bcel.generic.Type.NO_ARGS, null, "getParameterType", writerClassName, il, cp); il.append(new LDC_W(cp.addClass(enhanceInnerClassName(atc.getMethodArgType(), atc.getClazz().isMemberClass())))); il.append(InstructionFactory.ARETURN); paramType.setMaxStack(); cg.addMethod(paramType.getMethod()); if (LOG.isDebugEnabled()) { LOG.debug("return type method:\n{}", paramType.getMethod().getCode().toString(true)); } il.dispose(); return loadClass(loader, writerClassName, cg); } catch (Exception ite) { throw new UnableToCreateInstanceException(writerClassName, "Unable to instantiate class: " + writerClassName, ite); } } /** * @param configuration configuration name * baseDir - allows to set the directory where newly generated temp files for classes * will reside until the system exits. * readerCleanUpCycle - allows to set clean up cycle for soft cache of readers * writerCleanUpCycle - allows to set clean up cycle for soft cache of writers * @param value value to set * @return true if configuration was set, false if not set or invalid * @throws GeDAException any exceptions during configuration */ @Override public boolean configure(final String configuration, final Object value) throws GeDAException { if ("baseDir".equals(configuration) && value instanceof String) { final String dir = (String) value; if (dir.endsWith("/")) { this.baseDir = dir; } else { this.baseDir = dir + "/"; } LOG.info("Setting class loader base dir to: {}", this.baseDir); enhanceClassLoader(); return true; } return super.configure(configuration, value); } }