/* * 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. */ /* * BasicCALServices.java * Creation date: Feb 23, 2005. * By: Edward Lam */ package org.openquark.cal.services; import java.util.Collections; import java.util.LinkedHashSet; 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.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleSourceDefinition; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.PreludeTypeConstants; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.Scope; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.SourceModelModuleSource; import org.openquark.cal.compiler.SourceModelUtilities; import org.openquark.cal.compiler.TypeChecker; import org.openquark.cal.compiler.TypeExpr; import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo; import org.openquark.cal.compiler.io.EntryPointSpec; import org.openquark.cal.machine.StatusListener; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.runtime.CALExecutorException; import org.openquark.cal.runtime.ExecutionContext; import org.openquark.cal.runtime.MachineType; import org.openquark.cal.services.ProgramModelManager.CompilationOptions; /** * This is a helper class to assist CAL clients with creating and using some of the basic CAL services. * It handles initialization and compilation of a workspace, provision of some basic CAL service objects, and contains * some helper methods for filtering gems and running code. * * @author Edward Lam * @author Rick Cameron * @author mbyne */ public final class BasicCALServices { /** this is the default BasicCALServices configuration*/ private static final Config DEFAULT_CONFIG = makeConfig(); /** The associated workspace manager.*/ private final WorkspaceManager workspaceManager; /** handy TypeExpr constants for common Prelude types. */ private PreludeTypeConstants preludeTypeConstants; /** The provider from which the workspace manager will be initialized. */ private final WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider; /** the default compilation options */ private CompilationOptions compilerOptions = makeDefaultCompilationOptions(); /** used to cache entry points to reduce recompilation */ private final EntryPointCache entryPointCache; /** * This class is used to encapsulate BasicCALServices configuration properties. * * @author Magnus Byne */ public final static class Config { /**this defines the machine type used by the basic cal services*/ private MachineType machineType = MachineType.LECC; /** this defines the root provider - directory where lecc files are output*/ private WorkspaceManager.SourceGenerationRootOverrideProvider rootProvider = WorkspaceManager.DEFAULT_ROOT_OVERRIDER; /** this limits the number of entry points that can be cached per module*/ private int entryPointCacheSize = 10; /** this limits the number of modules for which entry points are cached*/ private int moduleCacheSize = 5; /** * Config cannot be instantiated directly - the factory method makeConfig in the containing * class must be used */ private Config() {} /** * Set the machine type of the configuration. * This option is limited to the package scope. * @param machineType */ void setMachineType(MachineType machineType) { this.machineType = machineType; } /** * Set the maximum number of entry points that can be cached for a particular module * @param entryPointCacheSize */ public void setEntryPointCacheSize(int entryPointCacheSize) { this.entryPointCacheSize = entryPointCacheSize; } /** * Set the maximum number of modules for which entry points will be cached * @param moduleCacheSize the number of modules to cache entry points for */ public void setModuleCacheSize(int moduleCacheSize) { this.moduleCacheSize = moduleCacheSize; } /** * Set the root directory provider - this controls where compiled modules are stored. * @param rootProvider */ public void setSourceRootProvider(WorkspaceManager.SourceGenerationRootOverrideProvider rootProvider) { this.rootProvider = rootProvider; } } /** * Constructor for a BasicCALServices object * @param workspaceManager The workspace manager to be used. * @param workspaceFileProperty * If non-null, the value of this system property represents the file system pathname of the workspace file * to be used to instantiate the workspace. * If null, defaultWorkspaceFileName will be used. * @param defaultWorkspaceFileName This workspace file from the StandardVault will be used if the workspaceFileProperty * system property is not set. */ private BasicCALServices (WorkspaceManager workspaceManager, String workspaceFileProperty, String defaultWorkspaceFileName, Config config) { if (workspaceManager == null) { throw new NullPointerException("Argument workspaceManager may not be null."); } this.workspaceDeclarationProvider = DefaultWorkspaceDeclarationProvider.getDefaultWorkspaceDeclarationProvider(workspaceFileProperty, defaultWorkspaceFileName); this.workspaceManager = workspaceManager; workspaceManager.getProgramManager(); workspaceManager.getProgramManager(); this.entryPointCache = new EntryPointCache(workspaceManager, config.entryPointCacheSize, config.moduleCacheSize); } /** * The simplest factory method to create a BasicCALServices instance. * @param workspaceFileName name of the workspace from the StandardVault e.g. "cal.platform.test.cws" * * @return the BasicCALServices instance, or null if initialization failed */ public static BasicCALServices make (String workspaceFileName) { return make(null, workspaceFileName, null); } /** * The simplest factory method to create a BasicCALServices instance. * It ensures that the CAL workspace is compiled and ready to use. * This is all that is needed in most cases. * @param workspaceFileName * @param messageLogger all messages created during the compilation of the workspace are appended to the messageLogger * @return the compiled CAL services, or null if initialization or compilation failed */ public static BasicCALServices makeCompiled (String workspaceFileName, CompilerMessageLogger messageLogger) { BasicCALServices res = make(workspaceFileName); if (res == null) { return null; } boolean compiled = res.compileWorkspace(null, messageLogger); if (!compiled) { return null; } return res; } /** * Factory method for this class. * @param workspaceFileProperty * If non-null, the value of this system property represents the file system pathname of the workspace file * to be used to instantiate the workspace. * If null, defaultWorkspaceFileName will be used. * @param defaultWorkspaceFileName This workspace file from the StandardVault will be used if the workspaceFileProperty * system property is not set. * @param defaultWorkspaceClientID a string identifying the default client for this workspace. * Client id's are used to refer to specific workspaces. This id will be used, unless the property * indicated by CALWorkspace.WORKSPACE_PROP_CLIENT_ID is set, in which case the property value will be used. * If the client id is null, the workspace is nullary. * * @return the BasicCALServices instance, or null if initialization failed */ public static BasicCALServices make ( String workspaceFileProperty, String defaultWorkspaceFileName, String defaultWorkspaceClientID) { return make(workspaceFileProperty, defaultWorkspaceFileName, defaultWorkspaceClientID, DEFAULT_CONFIG); } /** * Factory method for this class. * @param workspaceFileProperty * If non-null, the value of this system property represents the file system pathname of the workspace file * to be used to instantiate the workspace. * If null, defaultWorkspaceFileName will be used. * @param defaultWorkspaceFileName This workspace file from the StandardVault will be used if the workspaceFileProperty * system property is not set. * @param defaultWorkspaceClientID a string identifying the default client for this workspace. * Client id's are used to refer to specific workspaces. This id will be used, unless the property * indicated by CALWorkspace.WORKSPACE_PROP_CLIENT_ID is set, in which case the property value will be used. * If the client id is null, the workspace is nullary. * @param config this parameter controls the configuration of the BasicCALServices that is created * @return the BasicCALServices instance, or null if initialization failed */ public static BasicCALServices make ( String workspaceFileProperty, String defaultWorkspaceFileName, String defaultWorkspaceClientID, Config config) { String clientID = WorkspaceConfiguration.getDiscreteWorkspaceID(defaultWorkspaceClientID); WorkspaceManager workspaceManager = WorkspaceManager.getWorkspaceManager(clientID, config.machineType, config.rootProvider); if (workspaceManager ==null) { return null; } else { return new BasicCALServices(workspaceManager, workspaceFileProperty, defaultWorkspaceFileName, config); } } /** makes a new BasicCALServices configuarion with default properties * @return a new default config */ public static Config makeConfig() { return new Config(); } /** * @return the workspace manager. */ public WorkspaceManager getWorkspaceManager() { return workspaceManager; } /** * Note: must have a compiled workspace in hand before these will be available. * @return handy TypeExpr constants for common Prelude types. */ public PreludeTypeConstants getPreludeTypeConstants() { if (preludeTypeConstants == null) { ModuleTypeInfo preludeModuleTypeInfo = workspaceManager.getModuleTypeInfo(CAL_Prelude.MODULE_NAME); if (preludeModuleTypeInfo == null) { throw new IllegalStateException("Must have a successfully compiled Cal.Core.Prelude module before getPreludeTypeConstants can be called."); } preludeTypeConstants = new PreludeTypeConstants(preludeModuleTypeInfo); } return preludeTypeConstants; } /** * Compile the workspace. * * @param programStatusListener if non-null, the listener on the status of the compilation. * @param logger the logger used to log error messages during compilation. * @return true if the compilation was successful */ public boolean compileWorkspace(StatusListener programStatusListener, CompilerMessageLogger logger) { entryPointCache.lockCache(); try { entryPointCache.flush(); // Load default prelude. if (logger == null) { logger = new MessageLogger (); } Status compileStatus = new Status("Compile status."); // Initialize the workspace. workspaceManager.initWorkspace(workspaceDeclarationProvider, compileStatus); if (compileStatus.getSeverity() != Status.Severity.OK) { logger.logMessage(compileStatus.asCompilerMessage()); return false; } boolean programCompilationSuccessful = makeProgram (this.workspaceManager, logger, programStatusListener); return programCompilationSuccessful; } finally { entryPointCache.unlockCache(); } } /** * Recompile the workspace. * @return true if the compilation was successful, false otherwise. */ public boolean recompileWorkspace (CompilerMessageLogger logger) { entryPointCache.lockCache(); try { entryPointCache.flush(); return makeProgram (this.workspaceManager, logger, null); } finally { entryPointCache.unlockCache(); } } /** * Builds the program object using the CAL file at the given location. * Sets the internal program member. * @return true if compilation was successful, false otherwise */ private boolean makeProgram(WorkspaceManager workspaceManager, CompilerMessageLogger logger, StatusListener statusListener) { if (logger == null) { logger = new MessageLogger (); } int nErrorsBefore = logger.getNErrors(); // Compile. workspaceManager.compile(logger, false, statusListener); return logger.getNErrors() == nErrorsBefore; } /** * @return the CAL workspace. */ public CALWorkspace getCALWorkspace () { return getWorkspaceManager().getWorkspace (); } /** * @return an associated Compiler */ public Compiler getCompiler() { return getWorkspaceManager().getCompiler(); } /** * @return the associated TypeChecker */ public TypeChecker getTypeChecker() { return getWorkspaceManager().getTypeChecker(); } /** * Get the type info for a module * @param moduleName the name of the module * @return the type info for the module */ public ModuleTypeInfo getModuleTypeInfo(ModuleName moduleName) { CALWorkspace workspace = getCALWorkspace(); if (workspace == null) { return null; } return workspace.getMetaModule(moduleName).getTypeInfo(); } /** * Get the type checker info for a module * @return the TypeCheckInfo for a module */ public TypeCheckInfo getTypeCheckInfo(ModuleName moduleName) { return getTypeChecker ().getTypeCheckInfo (moduleName); } /** * Returns the entity corresponding to the specified qualified name. * @param gemName the name of the entity to retrieve. * @return the corresponding gem entity, or null if it is not found. */ public GemEntity getGemEntity(QualifiedName gemName) { return getWorkspaceManager().getWorkspace().getGemEntity(gemName); } /** * This returns a type expression for the type represented by the qualified name * the type expression is evaluated within the scope of the type's defining module * @param name the types qualified name * @return type expression representing the qualified type name */ public TypeExpr getTypeFromQualifiedName(QualifiedName name) { return getTypeFromString(name.getModuleName(), name.getQualifiedName()); } /** * Returns the type expression corresponding to the specified string. * @param module the type string is compiled w.r.t to this module * @param typeString a string representation a CAL type. * @return a type expression object corresponding to the string, or null if the string couldn't be converted to a type. */ public TypeExpr getTypeFromString(ModuleName module, String typeString) { return getTypeChecker().getTypeFromString(typeString, module, null); } /** * Returns public gems in the workspace that match the given filter * * @param filter a gem filter. * @return public gems in the workspace which pass the filter. */ public Set<GemEntity> getMatchingGems(GemFilter filter) { return getMatchingGems(filter, true); } /** * Returns public gems in the workspace that match the given filter. * * @param filter a gem filter. * @param sortGems If true, then the gems will be sorted alphabetically. * @return public gems in the workspace which pass the filter. */ public Set<GemEntity> getMatchingGems(GemFilter filter, boolean sortGems) { GemViewer gemViewer = new GemViewer(); // Sort the gems alphabetically, if specified. if (sortGems) { gemViewer.setSorter (new GemQualifiedNameCaseInsensitiveSorter ()); } gemViewer.addFilter(filter); return getVisibleGemEntities(gemViewer); //perspective.getVisibleGemEntities(); } /** * Get public gems in the workspace. * @param gemEntityViewer the gem viewer used to filter the gems returned * @return public gems in the workspace which visible by the given viewer */ private Set<GemEntity> getVisibleGemEntities(GemViewer gemEntityViewer) { CALWorkspace workspace = getCALWorkspace(); Set<GemEntity> visibleEntitySet = new LinkedHashSet<GemEntity>(); // add all visible entities from visible modules int nVisibleModules = workspace.getNMetaModules(); // getNVisibleMetaModules(); for (int i = 0; i < nVisibleModules; i++) { MetaModule nthModule = workspace.getNthMetaModule(i); visibleEntitySet.addAll(getVisibleGemEntities(nthModule, gemEntityViewer));// getVisibleGemEntities(nthModule)); } // Apply the view policy if any. if (gemEntityViewer != null) { visibleEntitySet = new LinkedHashSet<GemEntity>(gemEntityViewer.view(visibleEntitySet)); } return visibleEntitySet; } /** * Get the public gems in a given module. * @param module the module in which to look. * @return Set (GemEntity) the set of visible entities. */ private Set<GemEntity> getVisibleGemEntities(MetaModule module, GemViewer gemEntityViewer) { // Check that module != null Assert.isNotNullArgument(module, "module"); int nEntities = module.getNGemEntities(); // Add all entities from the working module, or only public entities from non-working modules. Set<GemEntity> visibleEntitySet = new LinkedHashSet<GemEntity>(); for (int i = 0; i < nEntities; i++) { GemEntity gemEntity = module.getNthGemEntity(i); if (gemEntity.getScope() == Scope.PUBLIC) { visibleEntitySet.add(gemEntity); } } // Apply the view policy if any. if (gemEntityViewer != null) { visibleEntitySet = new LinkedHashSet<GemEntity>(gemEntityViewer.view(visibleEntitySet)); } return visibleEntitySet; } /** * Returns a Set containing the public gems in the workspace * which have the specified type signature. * @param scopeModule this is the module that is used for scope * @param typeName the type signature of the gems to be found * @param sortGems If true, then the gems will be sorted alphabetically. * @return a set of the matching gems */ public Set<GemEntity> findGemsOfType(ModuleName scopeModule, String typeName, boolean sortGems) { // Get the type expression for the type name. final TypeExpr typeExpr = getTypeFromString(scopeModule, typeName); if (typeExpr == null) { return Collections.emptySet(); } // Find all gems matching this type. final ModuleTypeInfo targetModuleTypeInfo = getModuleTypeInfo(scopeModule); GemFilter filter = new GemFilter() { @Override public boolean select(GemEntity gemEntity) { TypeExpr gemType = gemEntity.getTypeExpr(); return TypeExpr.canPatternMatch(gemType, typeExpr, targetModuleTypeInfo); } }; return getMatchingGems(filter, sortGems); } /** * Returns a set containing the public gems in the workspace which have * the specified return type. * @param scopeModule this is the module that is used for compiling the type * @param returnTypeName the type name of the return type for the gems to be found * @param sortGems If true, then the gems will be sorted alphabetically. * @return (Set of GemEntity) a set of the matching gems */ public Set<GemEntity> findGemsByReturnType(ModuleName scopeModule, String returnTypeName, boolean sortGems) { // Get the type expression for the type name. final TypeExpr returnTypeExpr = getTypeFromString(scopeModule, returnTypeName); if (returnTypeExpr == null) { return Collections.emptySet(); } // Find all gems matching this type. final ModuleTypeInfo targetModuleTypeInfo = getModuleTypeInfo(scopeModule); GemFilter filter = new GemFilter() { @Override public boolean select(GemEntity gemEntity) { TypeExpr gemResultType = gemEntity.getTypeExpr().getResultType(); return TypeExpr.canPatternMatch(gemResultType, returnTypeExpr, targetModuleTypeInfo); } }; return getMatchingGems(filter, sortGems); } /** * Compile the source definition for a module and add it to the program * @param sourceDefinition the module's source definition. * @param logger the logger used to log error messages during compilation. * @return the max error severity from the compile. */ public CompilerMessage.Severity addNewModule(ModuleSourceDefinition sourceDefinition, CompilerMessageLogger logger) { entryPointCache.lockCache(); try { entryPointCache.flush(); return getWorkspaceManager().makeModule(sourceDefinition, logger, compilerOptions); } finally { entryPointCache.unlockCache(); } } /** * Add a new module containing the specified function to the program. * Imports are computed automatically based on the function. * If the module already exists its' contents are replaced by the funcDefn. * @param newModuleName * @param functionDefintion * @return an EntryPointSpec with default input/output policies for the new function. * @throws GemCompilationException */ public EntryPointSpec addNewModuleWithFunction(ModuleName newModuleName, SourceModel.FunctionDefn functionDefintion) throws GemCompilationException { return addNewModuleWithFunction(newModuleName, null, functionDefintion, null); } /** * Add a new module containing the specified function, type declaration and imports to the program. * If the module already exists its' contents are replaced by the funcDefn. * @param newModuleName * @param imports - if this is null the imports are computed based on the function definition * @param functionDefinition * @param functionTypeDeclaration the type declaration for the function, this may be null * @return an EntryPointSpec, with default input/output policies, for the new function. * @throws GemCompilationException */ public EntryPointSpec addNewModuleWithFunction(ModuleName newModuleName, SourceModel.Import[] imports, SourceModel.FunctionDefn functionDefinition, SourceModel.FunctionTypeDeclaration functionTypeDeclaration) throws GemCompilationException { if (newModuleName == null) { throw new NullPointerException("Argument newModuleName must not be null."); } if (functionDefinition == null) { throw new NullPointerException("Argument functionDefinition must not be null."); } //create the module containing the function SourceModel.ModuleDefn newModule = SourceModel.ModuleDefn.make( newModuleName, imports, functionTypeDeclaration == null ? new SourceModel.TopLevelSourceElement[] { functionDefinition } : new SourceModel.TopLevelSourceElement[] { functionTypeDeclaration, functionDefinition } ); //compute the necessary imports if (imports == null) { newModule = SourceModelUtilities.ImportAugmenter.augmentWithImports(newModule); } CompilerMessageLogger logger = new MessageLogger(); //add the module to the workspace CompilerMessage.Severity errorLevel = addNewModule(new SourceModelModuleSource(newModule), logger); if (errorLevel.compareTo(CompilerMessage.Severity.ERROR) >= 0) { throw new GemCompilationException("Error adding new module", logger); } return EntryPointSpec.make(QualifiedName.make(newModuleName, functionDefinition.getName())); } /** * Runs an existing CAL function and returns the result. * A new default execution context is created to run the function, and then destroyed. * It is worthwhile to consider * creating an execution context separately and using it for all calls to runFunction, as this * can be more efficient, allowing certain results to be cached between executions. * @see #runFunction(EntryPointSpec, ExecutionContext, Object[]) * * @param entryPointSpec defines the function to run and the input and output policies to use. * @param args the arguments to pass to the function - may be null if there are no arguments * @return the result of running the function. * @throws CALExecutorException * @throws GemCompilationException */ public Object runFunction(EntryPointSpec entryPointSpec, Object[] args) throws CALExecutorException, GemCompilationException { ExecutionContext executionContext = workspaceManager.makeExecutionContextWithDefaultProperties(); try { return runFunction(entryPointSpec, executionContext, args); } finally { workspaceManager.resetCachedResults(executionContext); } } /** * runs an existing function and returns the result. It uses the supplied execution context. * It is generally much more efficient to use this version of runFunction, as it allows * CAFs can be cached between calls. * @param entryPointSpec defines the function to run and the input and output policies to use. * @param args the arguments to pass to the function - may be null if there are no arguments. * @return the result of running the function. * @throws CALExecutorException * @throws GemCompilationException */ public Object runFunction(EntryPointSpec entryPointSpec, ExecutionContext executionContext, Object[] args) throws CALExecutorException, GemCompilationException { if (entryPointSpec == null) { throw new NullPointerException("Argument entryPointSpec must not be null."); } if (executionContext == null) { throw new NullPointerException("Argument executionContext must not be null."); } return entryPointCache.runFunction(entryPointSpec, args == null ? new Object[0] : args, executionContext); } /** * This method pre-caches a list of entryPointSpecs for invocation with runFunction. * It is not necessary to use this method before using run function, but in some * circumstances it can improve performance. When runFunction is invoked on an * entryPointSpec for the first time, the CAL compiler must do some work in the target module. * If you intend to use many entryPointSpecs for the same module, invoking this function * with a list of the entryPointSpecs allows the CAL compiler to prepare them all * in a single step, which can be more efficient. If you run only a single function (even many times) * in any given module, or are not concerned by the cost of the first call to runFunction * for each entryPointSpec, there is no need to use this function. * * @param entryPointSpecs a list of entry point specs that will be used later by runFunction. * @throws GemCompilationException */ public void prepareFunctions(List<EntryPointSpec> entryPointSpecs) throws GemCompilationException { entryPointCache.cacheEntryPoints(entryPointSpecs); } /** * Get compiler options */ public CompilationOptions getCompilationOptions() { return new CompilationOptions(compilerOptions); } /** * set the compilation options - if they are not set default compilation options will be used * @param options the compilation options to use when running code. */ public void setCompilationOptions(CompilationOptions options) { compilerOptions = new CompilationOptions(options); } /** * @return the default compilation options, with the for immediate use flag set to true. */ private static CompilationOptions makeDefaultCompilationOptions() { CompilationOptions options = new CompilationOptions(); options.setForImmediateUse(true); return options; } }