/* * 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. */ /* * Module.java * Creation date: (March 6, 2000) * By: Luke Evans */ package org.openquark.cal.machine; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.internal.machine.MachineFunctionImpl; import org.openquark.cal.internal.machine.lecc.LECCModule; import org.openquark.cal.internal.serialization.ModuleSerializationTags; import org.openquark.cal.internal.serialization.RecordInputStream; import org.openquark.cal.internal.serialization.RecordOutputStream; import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo; /** * Warning- this class should only be used by the CAL runtime implementation. It is not part of the * external API of the CAL platform. * <P> * Module collects together machine specific information about the contents of * a module and associates it with the MoudleTypeInfo for that module. * Modules contain the executable program elements and have a name. * Creation date: (3/6/00 9:48:37 AM) * @author LWE */ abstract public class Module { public static final String COMPILED_MODULE_SUFFIX = "cmi"; /** * The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by * the {@link #loadGeneratedCodeInfo} method. */ private static final short[] GENERATED_CODE_INFO_RECORD_TAGS = new short[] { ModuleSerializationTags.LECC_GENERATED_CODE_INFO, ModuleSerializationTags.G_GENERATED_CODE_INFO }; private static final int moduleSerializationSchema = 0; private static final int importSerializationSchema = 1; private static final int generatedCodeInfoSerializationSchema = 0; /** * The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by * the {@link #load} method. */ private static final short[] MODULE_RECORD_TAGS = new short[] { ModuleSerializationTags.LECC_MODULE, ModuleSerializationTags.G_MODULE }; /** A lock for guarding access to {@link #functionNameToFunctionMap} and {@link #adjunctFunctionNameToFunctionMap}. */ // Note: this lock is private, and is not meant to be used by subclasses for their own locking needs. private final int[] lock = new int[0]; /** Information about the public entities exported by the module */ private final ModuleTypeInfo moduleTypeInfo; /** (String -> MachineFunction) the program entities */ /* @GuardedBy("lock") */ private final Map<String, MachineFunction> functionNameToFunctionMap; /** (String -> MachineFunction) any adjunct entities in this module. */ /* @GuardedBy("lock") */ private transient Map<String, MachineFunction> adjunctFunctionNameToFunctionMap; /** The classloader to use to resolve foreign classes for this module. */ private final ClassLoader foreignClassLoader; /** The GeneratedCodeInfo for the persisted form of this module. * This is set under these circumstances: * 1) During deserialization, this is set directly in the constructor. * 2) During compilation, this is set when CodeGenerator.GeneratedCodeInfo.write() is called. * * ie. if this module has been loaded or compiled to a persisted form, this member should always be set. * Otherwise null. */ private GeneratedCodeInfo generatedCodeInfo; /** * Constructor for a Module. * * @param name the name of the module * @param foreignClassLoader the classloader to use to resolve foreign classes for the module. */ protected Module(ModuleName name, ClassLoader foreignClassLoader) { if (name == null || foreignClassLoader == null) { throw new NullPointerException(); } this.foreignClassLoader = foreignClassLoader; functionNameToFunctionMap = new HashMap<String, MachineFunction>(); adjunctFunctionNameToFunctionMap = new HashMap<String, MachineFunction>(); moduleTypeInfo = new ModuleTypeInfo(name, this); } /** * Constructor for a Module. * * @param name the name of the module * @param foreignClassLoader the classloader to use to resolve foreign classes for the module. * @param generatedCodeInfo the CodeGenerator.GeneratedCodeInfo associated with this module, or null if none. */ protected Module(ModuleName name, ClassLoader foreignClassLoader, GeneratedCodeInfo generatedCodeInfo) { this(name, foreignClassLoader); this.generatedCodeInfo = generatedCodeInfo; } public final void clearAdjunctFunctions () { synchronized (lock) { adjunctFunctionNameToFunctionMap = new HashMap<String, MachineFunction>(); } } /** * Add a MachineFunction to this Module. * New function objects replace old ones with the same name. * @param machineFunction the label to add */ public final void addFunction(MachineFunction machineFunction) { synchronized (lock) { if (machineFunction.isForAdjunct()) { adjunctFunctionNameToFunctionMap.put(machineFunction.getName(), machineFunction); } else { functionNameToFunctionMap.put(machineFunction.getName(), machineFunction); } } } /** * Fetch a function by name. * @param functionName * @return the MachineFunction instance for the named function. Null if it doesn't exist. */ public MachineFunction getFunction (String functionName) { // Check functions in the module and adjunct both. synchronized (lock) { MachineFunction mf = functionNameToFunctionMap.get(functionName); if (mf == null) { mf = adjunctFunctionNameToFunctionMap.get(functionName); } return mf; } } /** * Fetch a label by name. * The label can be in this module, in an imported module, in an import of an imported module, etc. * @param functionName - the qualified name of a function * @return the MachineFunction instance for the named function. Null if it doesn't exist. */ public MachineFunction getFunction (QualifiedName functionName) { synchronized (lock) { if (functionName.getModuleName().equals(getName())) { return getFunction(functionName.getUnqualifiedName()); } else { Module m = findModule (functionName.getModuleName()); if (m != null) { return m.getFunction(functionName); } } // No label or other horridness return null; } } /** * * @param moduleName * @return The named module, or null if it cannot be referenced from this module * (ie. it is not this module, one of its imports, an import of its imports, etc..) */ public final Module findModule (ModuleName moduleName) { if (this.getName().equals (moduleName)) { return this; } ModuleTypeInfo foundTypeInfo = getModuleTypeInfo().getDependeeModuleTypeInfo(moduleName); return foundTypeInfo == null ? null : foundTypeInfo.getModule(); } /** * Return a <b>copy</b> of the functions in this module. * * @return a copy of the functions in this module in a new Collection object. */ public final Collection<MachineFunction> getFunctions () { /* * THREAD-SAFETY ISSUE: * this needs to be a copy, and not an unmodifiable view based on the underlying * collection (e.g. via Collections.unmodifiableCollection()), because iterating through * such a list is incompatible with concurrent modification (it would result in a * java.util.ConcurrentModificationException). Such a scenario will occur * with compilations taking place simultaneously on multiple threads. */ synchronized (lock) { List<MachineFunction> l = new ArrayList<MachineFunction> (functionNameToFunctionMap.values()); if(adjunctFunctionNameToFunctionMap.size() > 0) { l.addAll(adjunctFunctionNameToFunctionMap.values()); } return l; } } /** * @return The number of functions currently in this module. */ public final int getNFunctions() { synchronized (lock) { return functionNameToFunctionMap.size() + adjunctFunctionNameToFunctionMap.size(); } } /** * Get the name of the module. * @return the name of the module */ public final ModuleName getName() { return moduleTypeInfo.getModuleName(); } public final ModuleTypeInfo getModuleTypeInfo() { return moduleTypeInfo; } /** * Returns true if this module depends on the named module. * @param moduleName * @return boolean */ public final boolean dependsOn (ModuleName moduleName) { return (moduleTypeInfo.getImportedModule(moduleName) != null); } /** * Disassemble the code in the Program. * @return a string representation of the code */ @Override public final String toString() { StringBuilder sb = new StringBuilder("module "); sb.append(getName()); sb.append('\n'); // For each code contribution synchronized (lock) { for (final Map.Entry<String, MachineFunction> entry : functionNameToFunctionMap.entrySet()) { MachineFunction label = entry.getValue(); sb.append (label.toString() + "\n"); } } return sb.toString(); } /** * Write out the imported modules to the RecordOutputStream. * @param s * @throws IOException */ private void writeImports (RecordOutputStream s) throws IOException { // Write out a record that contains the module name // and the names of all the modules it depends on. s.startRecord(ModuleSerializationTags.MODULE_IMPORTS, importSerializationSchema); s.writeModuleName(getName()); int nImports = moduleTypeInfo.getNImportedModules(); s.writeInt(nImports); for (int i = 0; i < nImports; ++i) { ModuleTypeInfo mti = moduleTypeInfo.getNthImportedModule(i); s.writeModuleName(mti.getModuleName()); } s.endRecord(); } /** * Write meta information about the serialized module. * Timestamp of serialization, information about the generated code (e.g. version), etc. * @param s * @throws IOException */ private void writeSerializationInfo (RecordOutputStream s) throws IOException { // The usual pattern is to write the record for the subclass first, followed by the // record for the superclass. However, in this case we write the superclass record // first (ie, SERIALIZATION_INFO before LECC_GENERATED_CODE_INFO or G_GENERATED_CODE_INFO) // because CompiledModuleSourceDefinition.getSerializedTimestamp needs to be able to skip // straight to the SERIALIZATION_INFO record. // // So, if you change the format of the SERIALIZATION_INFO record, you will probably need to change // CompiledModuleSourceDefinition.getSerializedTimestamp and LECCModule.readLeccSpecificSerializationInfo // as well. s.startRecord(ModuleSerializationTags.SERIALIZATION_INFO, generatedCodeInfoSerializationSchema); s.writeLong(System.currentTimeMillis()); writeMachineSpecificSerializationInfo(s); s.endRecord(); } /** * Write out machine-specific meta information about the serialized module * @param s * @throws IOException */ abstract protected void writeMachineSpecificSerializationInfo(RecordOutputStream s) throws IOException; /** * Write out the content specific to the Module class. * @param s * @throws IOException */ protected void writeContent (RecordOutputStream s) throws IOException { s.startRecord (ModuleSerializationTags.MODULE, moduleSerializationSchema); moduleTypeInfo.write (s); synchronized (lock) { s.writeInt(functionNameToFunctionMap.size()); for (final Map.Entry<String, MachineFunction> entry : functionNameToFunctionMap.entrySet()) { MachineFunctionImpl mf = (MachineFunctionImpl)entry.getValue(); mf.write (s); } } s.endRecord (); } /** * Write this Module instance out to the RecordOutputStream. * @param s * @throws IOException */ public void write (RecordOutputStream s) throws IOException { // Write out the information about the generated code in // this module. writeSerializationInfo(s); // Write out the imports information. writeImports(s); // Now delegate to the derived class. writeActual(s); } /** * Write out the Module instance. * @param s * @throws IOException */ abstract protected void writeActual (RecordOutputStream s) throws IOException; /** * Read the contents of this Module, specific to the Module class, from the RecordInputStream. * The read position will be before the Module record header. * @param s * @param otherModules * @param msgLogger the logger to which to log deserialization messages. * @throws IOException */ protected void readContent (RecordInputStream s, Map<ModuleName, Module> otherModules, CompilerMessageLogger msgLogger) throws IOException { // At this point we should be at the beginning of the Module record. RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.MODULE); if (rhi == null) { throw new IOException ("Unable to find Module record."); } if (rhi.getSchema() > moduleSerializationSchema) { throw new IOException("Saved schema is greather than current schema in Module."); } moduleTypeInfo.readContent(s, otherModules, msgLogger); synchronized (lock) { int nFunctions = s.readInt(); for (int i = 0; i < nFunctions; ++i) { MachineFunction mf = loadMachineFunction(s, getModuleTypeInfo(), msgLogger); functionNameToFunctionMap.put(mf.getName(), mf); } } s.skipRestOfRecord(); } /** * This method is used to delegate loading of MachineFunciton instances * to concrete extensions of Module. The sub-classes of module contain * the information about what class to actually load. * @param s * @param mti * @param msgLogger * @return an instance of MachineFunction * @throws IOException */ protected abstract MachineFunction loadMachineFunction(RecordInputStream s, ModuleTypeInfo mti, CompilerMessageLogger msgLogger) throws IOException; /** * Read module import information and return a Set of dependee module names. * @param s * @return Set of ModuleName representing the imported modules. * @throws IOException */ public static Set<ModuleName> readDependencies (RecordInputStream s) throws IOException { RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.MODULE_IMPORTS); if (rhi == null) { throw new IOException ("Unable to find module dependencies record."); } if (rhi.getSchema() > importSerializationSchema) { throw new IOException("Saved schema is greather than current schema in Module."); } if (rhi.getSchema() < importSerializationSchema) { throw new IOException("Saved schema is less than current schema in Module. Earlier import serialization schemas are not supported."); } /* ModuleName moduleName = */ s.readModuleName(); int nImports = s.readInt(); Set<ModuleName> set = new HashSet<ModuleName>(); for (int i = 0; i < nImports; ++i) { ModuleName imported = s.readModuleName(); set.add(imported); } s.skipRestOfRecord(); return set; } /** * @return whether Expressions must be loaded during deserialization. * For instance, this will be false if usable generated code exists in the resource repository. */ public abstract boolean mustLoadExpressions(); /** * Read the generated code info from the RecordInputStream. * @param s * @return the module-specific GeneratedCodeInfo. * @throws IOException */ public static final GeneratedCodeInfo loadGeneratedCodeInfo(RecordInputStream s) throws IOException { // Load the record header and determine which class this is. RecordHeaderInfo rhi = s.findRecord(GENERATED_CODE_INFO_RECORD_TAGS); if (rhi == null) { throw new IOException ("Unable to find module record."); } if (rhi.getRecordTag() == ModuleSerializationTags.LECC_GENERATED_CODE_INFO) { return LECCModule.loadCodeInfo(s, rhi.getSchema()); // read the module name and the generated code info. } else if (rhi.getRecordTag() == ModuleSerializationTags.G_GENERATED_CODE_INFO) { throw new IOException ("Loading of g-machine specific compiled modules is not yet supported."); } else { throw new IOException ("Unexpected record tag found in Module.read: " + rhi.getRecordTag()); } } /** * Load one of the concrete subclasses of Module. * @param s * @param loadedModules - map of module names to loaded modules. * @param foreignClassLoader the classloader to use to resolve foreign classes for the module. * @param generatedCodeInfo The GeneratedCodeInfo for the persisted form of this module. * @param msgLogger the logger to which to log deserialization messages. * This should be used to log user (non-internal) errors only. * @return the new Module instance. * If there was a problem reading the module, null is returned and errors will be logged to the msg logger. * * @throws IOException if any internal errors occurred while loading the module. */ public final static Module load (RecordInputStream s, Map<ModuleName, Module> loadedModules, ClassLoader foreignClassLoader, GeneratedCodeInfo generatedCodeInfo, CompilerMessageLogger msgLogger) throws IOException { // Load the record header and determine which class this is. RecordHeaderInfo rhi = s.findRecord(MODULE_RECORD_TAGS); if (rhi == null) { throw new IOException ("Unable to find module record."); } if (rhi.getRecordTag() == ModuleSerializationTags.LECC_MODULE) { return LECCModule.load(s, rhi.getSchema(), loadedModules, foreignClassLoader, generatedCodeInfo, msgLogger); } else if (rhi.getRecordTag() == ModuleSerializationTags.G_MODULE) { throw new IOException ("Loading of g-machine specific compiled modules is not yet supported."); } else { throw new IOException ("Unexpected record tag found in Module.read: " + rhi.getRecordTag()); } } /** * @return a Set of String which contains the names of all direct * and indirect dependee modules. */ public final Set<ModuleName> getDependeeModuleNames () { return getDependeeModuleNames (new HashSet<ModuleName>()); } /** * Build up a list of module names for all direct and * indirect dependee modules. * @param names - The set of dependee module names thus far * @return Set of String, all direct and indirect dependee module names. */ private final Set<ModuleName> getDependeeModuleNames (Set<ModuleName> names) { for (int i = 0, n = getModuleTypeInfo().getNImportedModules(); i < n; ++i) { ModuleTypeInfo importedMTI = getModuleTypeInfo().getNthImportedModule(i); if (!names.contains(importedMTI.getModuleName())) { names.add(importedMTI.getModuleName()); importedMTI.getModule().getDependeeModuleNames(names); } } return names; } /** * @return Returns the classloader to use to resolve foreign classes for this module. * Never null. */ public final ClassLoader getForeignClassLoader() { return foreignClassLoader; } /** * @return the generatedCodeInfo, or null if this module has not been persisted to or loaded from the repository. */ public final GeneratedCodeInfo getGeneratedCodeInfo() { return generatedCodeInfo; } /** * @param generatedCodeInfo the generatedCodeInfo to set */ protected final void setGeneratedCodeInfo(GeneratedCodeInfo generatedCodeInfo) { this.generatedCodeInfo = generatedCodeInfo; } }