/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * LECCJavaBytecodeGenerator.java * Creation date: Oct 18, 2006. * By: Edward Lam */ package org.openquark.cal.internal.machine.lecc; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.zip.Deflater; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.internal.javamodel.AsmJavaBytecodeGenerator; import org.openquark.cal.internal.javamodel.BytecodeDebuggingUtilities; import org.openquark.cal.internal.javamodel.JavaClassRep; import org.openquark.cal.internal.javamodel.JavaGenerationException; import org.openquark.cal.internal.javamodel.JavaTypeName; import org.openquark.cal.internal.machine.CodeGenerationException; import org.openquark.cal.internal.machine.lecc.LECCModule.FunctionGroupInfo; import org.openquark.cal.machine.AsynchronousFileWriter; import org.openquark.cal.machine.ProgramResourceLocator; import org.openquark.cal.machine.ProgramResourceRepository; import org.openquark.cal.machine.StatusListener; /** * @author Bo Ilic, Raymond Cypher */ public class LECCJavaBytecodeGenerator extends JavaGenerator { /** * Calls a helper method which performs various checks on the generated bytecode. Most notable is a byte code verifier. * Individual tests and debug output can be turned on by setting the boolean variables in the debugGeneratedBytecode method below. * Note that these tests slow down bytecode generation considerably. */ private static final boolean DEBUG_GENERATED_BYTECODE = false; /** The deflater object. * Do not create and free as needed, as this may lead to OutOfMemoryErrors if too many are created * (Sun bug id 4797189). */ private final Deflater deflater = new Deflater(); /** The folder to which to write the Java files for the current module. */ private final ProgramResourceLocator.Folder moduleResourceFolder; /** The class loader for the module associated with this generator. */ private final CALClassLoader classLoader; /** Flag indicating that generated classes should be preemptively loaded. */ private final boolean immediateUse; /** Flag indicating that generated classes are for an adjunct. */ private final boolean forAdjunct; private final LECCModule module; /** * responsible for writing the class files generated on another serialization thread. * Note that this object is owned by the CodeGenerator and shared between all modules * being serialized. */ private final AsynchronousFileWriter classFileWriter; /** * A set of the names of the class files generated by this generator. */ private final Set<String> generatedClassFiles = new HashSet<String>(); /** * Constructor for a LECCJavaBytecodeGenerator * @param module The module for which java classes will be generated. * @param resourceRepository The repository of program resources. * @param forAdjunct * @param immediateUse * @param classFileWriter * @throws IOException if there was a problem creating the repository folder where the source generation files will exist. */ LECCJavaBytecodeGenerator(LECCModule module, ProgramResourceRepository resourceRepository, boolean forAdjunct, boolean immediateUse, AsynchronousFileWriter classFileWriter) throws IOException { this.module = module; this.moduleResourceFolder = CodeGenerator.getModuleResourceFolder(module.getName()); if (!immediateUse && !forAdjunct) { // Ensure the module repository folder exists. // throws IOException. resourceRepository.ensureFolderExists(moduleResourceFolder); } this.classLoader = module.getClassLoader(); this.immediateUse = immediateUse; this.forAdjunct = forAdjunct; this.classFileWriter = classFileWriter; } /** {@inheritDoc} */ @Override void createFunction(FunctionGroupInfo functionGroupInfo, boolean forceWrite, CompilerMessageLogger logger) throws CodeGenerationException { if (forceWrite) { // Get the sc definition. JavaClassRep classRep = JavaDefinitionBuilder.getSCDefinition(functionGroupInfo, module, getCodeGenerationStats()); // Generate and write classes. writeClassWithInnerClasses(classRep, logger); informStatusListeners(StatusListener.SM_ENTITY_GENERATED_FILE_WRITTEN, functionGroupInfo.getFunctionGroupName()); } else { informStatusListeners(StatusListener.SM_ENTITY_GENERATED, functionGroupInfo.getFunctionGroupName()); } } /** {@inheritDoc} */ @Override void createTypeDefinition(TypeConstructor typeConstructor, boolean forceWrite, CompilerMessageLogger logger) throws CodeGenerationException { // Write out the bytecode for the type constructor and associated data constructors. if (forceWrite) { // Get the data type definition. JavaClassRep classRep = JavaDefinitionBuilder.getDataTypeDefinition(typeConstructor, module, getCodeGenerationStats()); // Generate and write classes. writeClassWithInnerClasses(classRep, logger); for (int j = 0; j <= typeConstructor.getNDataConstructors(); ++j) { informStatusListeners(StatusListener.SM_ENTITY_GENERATED_FILE_WRITTEN, typeConstructor.getName().getUnqualifiedName()); } } else { for (int j = 0; j <= typeConstructor.getNDataConstructors(); ++j) { informStatusListeners(StatusListener.SM_ENTITY_GENERATED, typeConstructor.getName().getUnqualifiedName()); } } } /** {@inheritDoc} */ @Override void wrap() throws CodeGenerationException { // There is no cleanup/finalization for the bytecode generator to do at the end of generating code // for a module. } private void writeClassWithInnerClasses(JavaClassRep classRep, CompilerMessageLogger logger) throws CodeGenerationException { try { byte[] bytecode = AsmJavaBytecodeGenerator.encodeClass(classRep); writeClass(classRep, bytecode, logger); for (int i = 0, nInnerClasses = classRep.getNInnerClasses(); i < nInnerClasses; ++i) { JavaClassRep innerClassRep = classRep.getInnerClass(i); writeClassWithInnerClasses(innerClassRep, logger); } } catch (JavaGenerationException e) { throw new CodeGenerationException(e.getLocalizedMessage(), e); } } /** * Write out a class to a file. * @param classRep the source model representation of the class * @param bytecode the generated byte code for the class i.e. what actually to save. * @param logger the logger to use for logging error messages. */ private void writeClass(JavaClassRep classRep, byte[] bytecode, CompilerMessageLogger logger) { // figure out the unqualified name of the class. JavaTypeName typeName = classRep.getClassName(); String className = typeName.getUnqualifiedJavaSourceName().replace('.', '$'); if (immediateUse) { classLoader.defineClass(typeName.getName(), bytecode, forAdjunct); } else { // Deflate the class data. // Note that it is better to perform deflation on the current thread than to get // the asynchronous file writer thread to do it, since that thread is already the bottleneck. deflater.reset(); deflater.setInput(bytecode); deflater.finish(); byte[] buf = new byte[bytecode.length]; while (!deflater.finished()) { deflater.deflate(buf); } int deflatedSize = deflater.getTotalOut(); byte[] dataToWrite = new byte[deflatedSize]; System.arraycopy(buf, 0, dataToWrite, 0, deflatedSize); String fileName = className + ".lc"; ProgramResourceLocator.File classFileLocator = moduleResourceFolder.extendFile(fileName); //adds to the class writing queue and returns immediately //saving of class files is done on another thread. classFileWriter.addFileToWrite(new AsynchronousFileWriter.FileData(classFileLocator, dataToWrite), logger); // Add to the set of generated class files. generatedClassFiles.add (fileName); } if (DEBUG_GENERATED_BYTECODE) { debugGeneratedBytecode(typeName, bytecode); } } /** * Get the class data for a cal entity by name. * @param module the module in which the entity exists. * @param unqualifiedClassName the name of the class representing the entity. * @return the class data, or null if the name is not the name of a class representing an entity generated in this module. * @throws CodeGenerationException */ static byte[] generateClassData(LECCModule module, String unqualifiedClassName) throws CodeGenerationException { JavaClassRep classRep = JavaDefinitionBuilder.getClassRep(module, unqualifiedClassName); if (classRep == null) { return null; } try { byte[] bytecode = AsmJavaBytecodeGenerator.encodeClass(classRep); if (DEBUG_GENERATED_BYTECODE) { debugGeneratedBytecode(classRep.getClassName(), bytecode); } return bytecode; } catch (JavaGenerationException e) { throw new CodeGenerationException(e.getLocalizedMessage(), e); } } /** * @return Returns the generatedClassFiles. */ @Override Set<String> getGeneratedClassFiles() { return new HashSet<String> (generatedClassFiles); } /** * Called if the static flag AsmJavaBytecodeGenerator.DEBUG_GENERATED_BYTECODE is set. * * Performs various checks on the generated bytecode. Most notable is a byte code verifier. * Individual tests and debug output can be turned on by setting the boolean variables in the * method body below. Note that these tests slow down bytecode generation considerably. * * Note: path names below are Windows only and you'll have to adjust according to your own machine. * This is for debug purposes only. * * @param typeName * @param bytecode */ private static void debugGeneratedBytecode(JavaTypeName typeName, byte[] bytecode) { final boolean dumpAsmifiedText = true; final boolean dumpDisassembledText = true; final boolean verifyClassFileFormat = true; //the ASM bytecode generator doesn't currently generate any debug op codes. final boolean skipDebugOpCodes = false; final boolean skipInnerClassAttributes = false; String className = typeName.getUnqualifiedJavaSourceName().replace('.', '$'); String packageName = typeName.getPackageName(); int lastDot = packageName.lastIndexOf('.'); String moduleName = packageName.substring(lastDot + 1).replaceAll("^cal_", ""); if (dumpAsmifiedText) { String asmifierDumpPath = "d:\\dev\\asmifierOutput\\asm\\" + moduleName + "\\" + className + ".txt"; BytecodeDebuggingUtilities.dumpAsmifiedText(asmifierDumpPath, bytecode, skipDebugOpCodes, skipInnerClassAttributes); } if (dumpDisassembledText) { String disassembyDumpPath = "d:\\dev\\disassembly\\asm\\" + moduleName + "\\" + className + ".txt"; BytecodeDebuggingUtilities.dumpDisassembledText(disassembyDumpPath, bytecode, skipDebugOpCodes, skipInnerClassAttributes); } if (verifyClassFileFormat) { BytecodeDebuggingUtilities.verifyClassFileFormat(moduleName + "\\" + className + ".class", bytecode); } } }