/* * 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. */ /* * ProgramModelManager.java * Creation date: Nov 7, 2005. * By: Edward Lam */ package org.openquark.cal.services; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openquark.cal.compiler.Compiler; import org.openquark.cal.compiler.CompilerMessage; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.EntryPointGenerator; import org.openquark.cal.compiler.ForeignContextProvider; import org.openquark.cal.compiler.MessageKind; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleSourceDefinition; import org.openquark.cal.compiler.ModuleSourceDefinitionGroup; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.ProgramModifier; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.TypeChecker; import org.openquark.cal.machine.CALExecutor; import org.openquark.cal.machine.MachineFunction; import org.openquark.cal.machine.MachineStatistics; import org.openquark.cal.machine.Module; import org.openquark.cal.machine.ProgramManager; import org.openquark.cal.machine.StatusListener; import org.openquark.cal.machine.Program.ProgramException; import org.openquark.cal.runtime.ExecutionContext; import org.openquark.cal.runtime.ExecutionContextProperties; import org.openquark.cal.runtime.MachineType; /** * This class presents a higher-level abstraction to the program manager. * It can manage queries and changes to the program without having to know about a program manager. * @author Edward Lam */ public class ProgramModelManager { /** The program manager. */ private final ProgramManager programManager; /** The set of StatusListeners that want to listen to program generation changes in the program model. */ private final Set<StatusListener> statusListeners; /** * Constructor for a program model manager. This is intended to be called * by the unit testing infrastructure to specify a custom ProgramManager. * * @param programManager the ProgramManager for this ProgramModelManager. */ public ProgramModelManager(ProgramManager programManager) { if (programManager == null) { throw new NullPointerException("Argument 'programManager' must not be null."); } this.programManager = programManager; this.statusListeners = Collections.synchronizedSet(new HashSet<StatusListener>()); } /** * Makes a new disposable copy of the program modifier. * @param options - the values for compiler options * @return a copy of the program modifier, either a new one, or the private copy held by this workspace manager. */ protected ProgramModifier makeProgramModifier(CompilationOptions options) { ProgramModifier newProgramModifier = programManager.makeProgramModifier(); for (final StatusListener statusListener : statusListeners) { newProgramModifier.addStatusListener(statusListener); } if (options != null) { newProgramModifier.setForceCodeRegen(options.isForceCodeRegeneration()); newProgramModifier.setForImmediateUse(options.isForImmediateUse()); newProgramModifier.setIgnoreCompiledModuleInfo(options.isIgnoreCompiledModuleInfo()); newProgramModifier.setForeignContextProvider(options.getCustomForeignContextProvider()); } return newProgramModifier; } /** * Makes a new disposable copy of the entry point generator. * @param options - the values for compiler options * @return a new entry point generator. */ private EntryPointGenerator makeEntryPointGenerator(CompilationOptions options) { EntryPointGenerator newEntryPointGenerator = programManager.makeEntryPointGenerator(); for (final StatusListener statusListener : statusListeners) { newEntryPointGenerator.addStatusListener(statusListener); } if (options != null) { newEntryPointGenerator.setForceCodeRegen(options.isForceCodeRegeneration()); newEntryPointGenerator.setForImmediateUse(options.isForImmediateUse()); // TODOEL: Should we provide some kind of warning if inapplicable options are set? // eg. Can't override the foreign context for a module. // ignoreCompiledModuleInfo doesn't apply. } return newEntryPointGenerator; } /** * Get the Compiler for this manager. * @return Compiler */ public Compiler getCompiler() { return getCompiler(new CompilationOptions()); } /** * Get the Compiler for this manager. * * @param compilationOptions the compilation options for the compiler. * @return Compiler */ public Compiler getCompiler(CompilationOptions compilationOptions) { Assert.isNotNullArgument(compilationOptions, "compilationOptions"); return makeEntryPointGenerator(compilationOptions); } /** * @return a type checker associated with this manager. */ public TypeChecker getTypeChecker() { // Make a new disposable copy of the type checker. return programManager.makeTypeCheckerImpl(); } /** * Factory method for creating a machine-specific execution context. * @param properties the ExecutionContextProperties instance encapsulating an immutable map of key-value pairs which is exposed * as system properties from within CAL. * * @return a new CALExecutor.Context instance. */ public ExecutionContext makeExecutionContext(ExecutionContextProperties properties) { return programManager.makeExecutionContext(properties); } /** * Factory method for creating a machine-specific execution context with default values for * the execution context's properties. * * @return a new CALExecutor.Context instance. */ public ExecutionContext makeExecutionContextWithDefaultProperties() { return programManager.makeExecutionContext(makeDefaultExectionContextProperties()); } /** * Factory method for creating a machine-specific executor. * * @param context * the execution context to be used by the executor. * @return a new CALExecutor instance. */ public CALExecutor makeExecutor(ExecutionContext context) { return programManager.makeExecutor(context); } /** * Factory method for creating a machine-specific executor with a new * execution context. * @param properties the ExecutionContextProperties instance encapsulating an immutable map of key-value pairs which is exposed * as system properties from within CAL. * * @return a new CALExecutor instance with a new execution context. */ public CALExecutor makeExecutorWithNewContext(ExecutionContextProperties properties) { return programManager.makeExecutor(programManager.makeExecutionContext(properties)); } /** * Factory method for creating a machine-specific executor with a new * execution context and default values for the execution context's properties. * * @return a new CALExecutor instance with a new execution context. */ public CALExecutor makeExecutorWithNewContextAndDefaultProperties() { return programManager.makeExecutor(programManager.makeExecutionContext(makeDefaultExectionContextProperties())); } /** * @return a ExecutionContextProperties instance encapsulating the default values for the execution context's properties. */ private ExecutionContextProperties makeDefaultExectionContextProperties() { ExecutionContextProperties.Builder propertiesBuilder = new ExecutionContextProperties.Builder(); ExecutionContextProperties defaultProperties = propertiesBuilder.toProperties(); return defaultProperties; } /** * Compile the modules in this manager. * @param logger the logger used to log error messages. * @param dirtyModulesOnly if true, only dirty (uncompiled or changed) modules will be compiled. * @param statusListener a listener, possibly null, for the status of the compilation process */ public void compile(ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger, boolean dirtyModulesOnly, StatusListener statusListener) { compile(definitionGroup, logger, dirtyModulesOnly, statusListener, new CompilationOptions()); } /** * Compile the modules in this manager. * @param logger the logger used to log error messages. * @param dirtyModulesOnly if true, only dirty (uncompiled or changed) modules will be compiled. * @param statusListener a listener, possibly null, for the status of the compilation process * @param options - values for compilation options */ public void compile(ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger, boolean dirtyModulesOnly, StatusListener statusListener, CompilationOptions options) { if (definitionGroup == null) { logger.logMessage(new CompilerMessage(new MessageKind.Fatal.CouldNotFindWorkspaceDefinition())); return; } ProgramModifier newProgramModifier = makeProgramModifier(options); if (statusListener != null) { newProgramModifier.addStatusListener(statusListener); } try { newProgramModifier.compile(definitionGroup, logger, dirtyModulesOnly); } finally { if (statusListener != null) { newProgramModifier.removeStatusListener(statusListener); } } } /** * @return the program manager. */ protected final ProgramManager getProgramManager() { // TODOEL: remove this method!(?) return programManager; } /** * Compile a module to the current program. * Dependent modules will be removed from the program. * @param sourceDef the ModuleSourceDefinition to compile. * @param logger the logger to use during the compile. * @return the highest error condition experienced */ public CompilerMessage.Severity makeModule (ModuleSourceDefinition sourceDef, CompilerMessageLogger logger) { return makeModule(sourceDef, logger, new CompilationOptions()); } /** * Compile a module to the current program. * Dependent modules will be removed from the program. * @param sourceDef the ModuleSourceDefinition to compile. * @param logger the logger to use during the compile. * @param options - values for the compilation options * @return the highest error condition experienced */ public CompilerMessage.Severity makeModule (ModuleSourceDefinition sourceDef, CompilerMessageLogger logger, CompilationOptions options) { // We want to always force code regeneration when compiling a module (vs. compiling the entire workspace). // This is partly to workaround the issue where compiling multiple SourceModelModuleSource // objects with the same timestamp back-to-back would erroneously supress the code // generation for the second and subsequent compilation attempts. CompilationOptions modifiedOptions = new CompilationOptions(options); modifiedOptions.setForceCodeRegeneration(true); ProgramModifier pm = makeProgramModifier(modifiedOptions); return pm.compile (sourceDef, logger); } /** * Compile module(s) to the current program. * Dependent modules will be removed if not included. * @param moduleNames a list of the names of the changed modules. * @param definitionGroup the definition group from which the module definitions may be retrieved. * @param logger the logger to use during the compile. * @return the highest error condition experienced */ public CompilerMessage.Severity makeModules (ModuleName[] moduleNames, ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger) { return makeModules (moduleNames, definitionGroup, logger, new CompilationOptions()); } /** * Compile module(s) to the current program. * Dependent modules will be removed if not included. * @param moduleNames a list of the names of the changed modules. * @param definitionGroup the definition group from which the module definitions may be retrieved. * @param logger the logger to use during the compile. * @param options - the values for compilation options * @return the highest error condition experienced */ public CompilerMessage.Severity makeModules (ModuleName[] moduleNames, ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger, CompilationOptions options) { // We want to always force code regeneration when compiling a given set of modules (vs. compiling the entire workspace). // This is partly to workaround the issue where compiling multiple SourceModelModuleSource // objects with the same timestamp back-to-back would erroneously supress the code // generation for the second and subsequent compilation attempts. CompilationOptions modifiedOptions = new CompilationOptions(options); modifiedOptions.setForceCodeRegeneration(true); ProgramModifier pm = makeProgramModifier(modifiedOptions); return pm.compile (moduleNames, definitionGroup, logger); } /** * @param listener StatusListener */ public void addStatusListener(StatusListener listener) { statusListeners.add(listener); } /** * @param listener StatusListener */ public void removeStatusListener(StatusListener listener) { statusListeners.remove(listener); } /** * Discarded any cached (memoized) results in this program. * @param context the context with which the cached results are associated. */ public void resetCachedResults(ExecutionContext context) { programManager.resetCachedResults (context); } /** * Discard any cached (memoized) results in the named module and any dependent modules. * @param moduleName * @param context the context with which the cached results are associated. */ public void resetCachedResults(ModuleName moduleName, ExecutionContext context) { programManager.resetCachedResults (moduleName, context); } /** * Discarded the machine state, including any cached (memoized) results in this program. * @param context the context with which the machine state is associated. */ public void resetMachineState(ExecutionContext context) { programManager.resetMachineState(context); } /** * Discard the machine state, including any cached (memoized) results in the named module and any dependent modules. * @param moduleName * @param context the context with which the machine state is associated. */ public void resetMachineState(ModuleName moduleName, ExecutionContext context) { programManager.resetMachineState(moduleName, context); } /** * Remove a module from the program. * Its dependents will also be removed. * * @param moduleName the name of the module to remove. * @return whether removing the module was successful. */ public final boolean removeModule (ModuleName moduleName) { makeProgramModifier(new CompilationOptions()).removeModule(moduleName, true); return true; } /** * * @param targetModule * @return the names of the dependent modules. */ public Set<ModuleName> getDependentModuleNames (ModuleName targetModule) { return programManager.getSetOfDependentModuleNames(targetModule, true); } /** * * @param moduleName * @return type info for the named module. Null if module doesn't exist. */ public ModuleTypeInfo getModuleTypeInfo (ModuleName moduleName) { return programManager.getModuleTypeInfo(moduleName); } /** * Gets the module with a given moduleName. * @return the module with a given module name * @param name the module name */ public Module getModule(ModuleName name) { return programManager.getModule(name); } /** * Return a list of Module object for modules contained the ProgramManager instance * encapsulated by this ProgramModleManager. * * @return a list of Module objects for the modules in this program. */ public List<Module> getModules() { return programManager.getModules(); } /** * Returns true iff the specified module is in the Program instance * encapsulated by this WorkspaceManager. * * @param moduleName * the name of the module to check. * @return true iff the specified module is in the program. */ public boolean hasModuleInProgram(ModuleName moduleName) { // this is purely a query on the program, so we do not check whether the module // is in the workspace return programManager.containsModule(moduleName); } /** * Returns the names of the modules contained by the Program instance * encapsulated by this WorkspaceManager. * * @return an array of module names for the modules in the program. */ public ModuleName[] getModuleNamesInProgram() { // this is purely a query on the program, and differs in semantics from // the method getModuleNames(), which returns the names of the modules in // the workspace rather than the program return programManager.getModuleNames(); } /** * Returns the number of functions in the specified module. * * @param moduleName * the name of the module. * @return the number of functions in the specified module, or 0 if the * module is not in the program. */ public int getNFunctionsInModule(ModuleName moduleName) { // this is purely a query on the program, so we do not check whether the module // is in the workspace return programManager.getNFunctionsInModule(moduleName); } /** * Retrieves the specified machine function. * * @param functionName * the qualified name of the function. * @return the specified machine function, or null if it does not exist. * @throws ProgramException * if the program does not contain the module for the function. */ public MachineFunction getMachineFunction(QualifiedName functionName) throws ProgramException { return programManager.getMachineFunction(functionName); } /** * Returns the machine type associated with this WorkspaceManager. * * @return the machine type. */ public MachineType getMachineType() { return programManager.getMachineType(); } /** * @return the machine statistics associated with this program. */ public MachineStatistics getMachineStatistics() { return programManager.getMachineStatistics(); } /** * @param moduleName * @return the machine statistics associated with the given module. */ public MachineStatistics getMachineStatisticsForModule(ModuleName moduleName) { return programManager.getMachineStatisticsForModule(moduleName); } /** * A class to encapsulate the various compilation options * when compiling a module or adjunct. * @author rcypher */ public static class CompilationOptions { /** Set to true if all existing generated code should be discarded and regenerated. */ private boolean forceCodeRegeneration; /** Set to true if any compiled module files (i.e. type info etc.) should be ignored. * This is used in situations where we want to force compilation of all modules * without necessarily forcing re-generation of machine specific code. * This is useful in situation where the user has manually deleted/modified * the generated machine-specific code. */ private boolean ignoreCompiledModuleInfo; /** Indicates to the compiler/code generator that the entities being compiled will be used immediately. */ private boolean forImmediateUse; /** Any custom foreign context provider specified by the client. */ private ForeignContextProvider customForeignContextProvider; public CompilationOptions () { } /** * Copy-constructor for CompilationOptions. * * This copy-constructor is meant to facilitate the task of making a * copy of an existing CompilationOptions instance so that the * compilation options can be modified without affecting the original * copy. * * @param other * the CompilationOptions instance to be copied. */ public CompilationOptions(CompilationOptions other) { this.forceCodeRegeneration = other.forceCodeRegeneration; this.ignoreCompiledModuleInfo = other.ignoreCompiledModuleInfo; this.forImmediateUse = other.forImmediateUse; this.customForeignContextProvider = other.customForeignContextProvider; } /** * @return Returns the forceCodeRegeneration. */ public boolean isForceCodeRegeneration() { return forceCodeRegeneration; } /** * @param forceCodeRegeneration The forceCodeRegeneration to set. */ public void setForceCodeRegeneration(boolean forceCodeRegeneration) { this.forceCodeRegeneration = forceCodeRegeneration; } /** * @return Returns the forImmediateUse. */ public boolean isForImmediateUse() { return forImmediateUse; } /** * @param forImmediateUse The forImmediateUse to set. */ public void setForImmediateUse(boolean forImmediateUse) { this.forImmediateUse = forImmediateUse; } /** * @return Returns the ignoreCompiledModuleInfo. */ public boolean isIgnoreCompiledModuleInfo() { return ignoreCompiledModuleInfo; } /** * @param ignoreCompiledModuleInfo The ignoreCompiledModuleInfo to set. */ public void setIgnoreCompiledModuleInfo(boolean ignoreCompiledModuleInfo) { this.ignoreCompiledModuleInfo = ignoreCompiledModuleInfo; } /** * @return Any custom foreign context provider specified by the client. Null if not specified. */ public ForeignContextProvider getCustomForeignContextProvider() { return customForeignContextProvider; } /** * @param customForeignContextProvider Set the custom foreign context provider to use. Null to use the default. * Note that in most cases it is sensible to set this as null. * This is provided as a way for the Eclipse dev tools to provide context not visible to the compiler. */ public void setCustomForeignContextProvider(ForeignContextProvider customForeignContextProvider) { this.customForeignContextProvider = customForeignContextProvider; } } }