/*
* 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.
*/
/*
* ProgramModifier.java
* Created: Feb 19, 2003 at 12:15:01 PM
* By: Raymond Cypher
*/
package org.openquark.cal.compiler;
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.CompilerMessage.Severity;
import org.openquark.cal.machine.CodeGenerator;
import org.openquark.cal.machine.Module;
import org.openquark.cal.machine.Program;
import org.openquark.cal.machine.StatusListener;
/**
* This is the ProgramModifier class.
*
* This class is used to compile modules.
* <p>
* Created: Feb 19, 2003 at 12:15:01 PM
* @author Raymond Cypher
*/
public abstract class ProgramModifier {
public static final String IGNORE_CMI_PROP = "org.openquark.cal.machine.lecc.internal.ignore_cmf";
/** This is a temporary flag used for debugging loading of compiled module files.
* If set, existing .cmi files will be ignored. */
private static final boolean IGNORE_COMPILED_MODULE_INFO = System.getProperty(IGNORE_CMI_PROP) != null;
private final CALCompiler compiler;
private final Program program;
/** Collection of status listeners */
private final Set<StatusListener> statusListeners = new HashSet<StatusListener>();
/** The program change listeners. */
private final Set<ProgramChangeListener> programChangeListeners = new HashSet<ProgramChangeListener>();
/** Flag used to indicate that all code should be regenerated. */
private boolean forceCodeRegen;
/** Flag to indicate that the code being compiled will be executing immediately.
* this allows for optimizations in generating/loading.
*/
private boolean forImmediateUse;
/**
* Flag to ignore compiled module info and re-construct from cal source.
*/
private boolean ignoreCompiledModuleInfo;
/** Any custom foreign context provider specified by the client. */
private ForeignContextProvider foreignContextProvider;
/**
* Constructor for ProgramModifier.
* @param program
*/
protected ProgramModifier(Program program) {
compiler = new CALCompiler();
if (program == null) {
throw new NullPointerException("Argument 'program' cannot be null.");
}
this.program = program;
// makes sure that the CALCompiler has a copy of the packager with the program
// from the very beginning
compiler.setPackager(makePackager(program));
// Add a listener to announce when a module is loaded.
addStatusListener(new StatusListener.StatusListenerAdapter() {
@Override
public void setModuleStatus(StatusListener.Status.Module moduleStatus, ModuleName moduleName) {
if (moduleStatus == StatusListener.SM_LOADED) {
fireModuleLoaded(ProgramModifier.this.program.getModule(moduleName));
}
}
});
}
abstract protected Packager makePackager(Program program);
abstract protected CodeGenerator makeCodeGenerator();
/**
* This function is used to retrieve a compiled module file written out to the same location as the
* generated sources associated with a specific machine.
* @param moduleName
* @return a CompiledModuleSourceDefinition if the compiled source is avaialable, otherwise null.
*/
abstract protected CompiledModuleSourceDefinition getCompiledSourceDefinition (ModuleName moduleName);
/**
* 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 final Severity compile(final ModuleSourceDefinition sourceDef, CompilerMessageLogger logger) {
if (sourceDef == null) {
throw new NullPointerException("Source definition must not be null.");
}
ModuleName sourceDefName = sourceDef.getModuleName();
if (sourceDefName == null) {
throw new NullPointerException("Source definition name must not be null.");
}
ModuleSourceDefinitionGroup sourceProvider = new ModuleSourceDefinitionGroup(new ModuleSourceDefinition[] {sourceDef});
ModuleName[] moduleNames = new ModuleName[] {sourceDefName};
// Defer to a more general compile().
return compile(moduleNames, sourceProvider, logger);
}
/**
* Compile all module(s) in the given source provider.
* @param definitionGroup the definition group from which the module definitions may be retrieved.
* @param logger the logger to use during the compile.
* @param dirtyModulesOnly if True, compiles only those modules modified since last compilation to the current program.
* If false, compiles all modules in the source provider to a new program.
* @return the highest error condition experienced
*/
public final CompilerMessage.Severity compile (ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger, boolean dirtyModulesOnly) {
Set<ModuleName> allModuleNames = new HashSet<ModuleName>();
for (int i = 0, nModules = definitionGroup.getNModules(); i < nModules; i++) {
ModuleSourceDefinition sourceDefinition = definitionGroup.getModuleSource(i);
allModuleNames.add (sourceDefinition.getModuleName());
}
if (!dirtyModulesOnly) {
return compileAllModules (allModuleNames, definitionGroup, logger);
}
List<ModuleName> dirtyModuleNames = new ArrayList<ModuleName>();
for (final ModuleName moduleName : allModuleNames) {
ModuleSourceDefinition sourceDefinition = definitionGroup.getModuleSource(moduleName);
Long timestamp = program.getCompilationTimestamp(moduleName);
if (timestamp != null) {
// Module was compiled before; check timestamp
if (timestamp.compareTo(new Long(sourceDefinition.getTimeStamp())) < 0 ) {
// Source was modified since last compilation
dirtyModuleNames.add(moduleName);
}
} else {
// Module was not compiled before; will be compiled
dirtyModuleNames.add(moduleName);
}
}
ModuleName[] dirtyModuleNamesArray = new ModuleName[dirtyModuleNames.size()];
dirtyModuleNames.toArray(dirtyModuleNamesArray);
return compile(dirtyModuleNamesArray, definitionGroup, logger);
}
/**
* Compile all module sources from the specified source definition group.
* @param moduleNames
* @param definitionGroup the module source definitions to compile.
* @param logger compiler message logger
* @return highest severity of the logged messages
*/
private final CompilerMessage.Severity compileAllModules (Set<ModuleName> moduleNames, ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger) {
// ensure the program has no modules in it.
for (final ModuleName moduleName : moduleNames) {
removeModuleInternal(moduleName, false);
}
Map<ModuleName, ModuleSourceDefinition> allSourceDefinitions = new HashMap<ModuleName, ModuleSourceDefinition>();
for (int i = 0, nModules = definitionGroup.getNModules(); i < nModules; i++) {
ModuleSourceDefinition ms = definitionGroup.getModuleSource(i);
if (ms != null) {
allSourceDefinitions.put(ms.getModuleName(), ms);
}
}
return compileSimple (moduleNames, allSourceDefinitions, logger);
}
/**
* Compile a number of modules in the given source definition group.
* @param moduleNames the names of the modules to compile..
* @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 final CompilerMessage.Severity compile (ModuleName[] moduleNames, ModuleSourceDefinitionGroup definitionGroup, CompilerMessageLogger logger) {
Set<ModuleName> moduleNamesSet = new HashSet<ModuleName>();
for (int i = 0; i < moduleNames.length; ++i) {
moduleNamesSet.add(moduleNames[i]);
}
Set<ModuleName> dependentModuleNames = program.getDependentModules(moduleNamesSet);
Set<ModuleName> allChangedModules = new HashSet<ModuleName>();
allChangedModules.addAll (moduleNamesSet);
allChangedModules.addAll (dependentModuleNames);
Map<ModuleName, ModuleSourceDefinition> sourceDefinitionMap = new HashMap<ModuleName, ModuleSourceDefinition>();
for (final ModuleName moduleName : allChangedModules) {
if (getProgram().getModule(moduleName) != null) {
removeModuleInternal (moduleName, false);
}
ModuleSourceDefinition newModuleSourceDefinition = definitionGroup.getModuleSource(moduleName);
if (newModuleSourceDefinition != null) {
sourceDefinitionMap.put (moduleName, newModuleSourceDefinition);
}
}
return compileSimple(allChangedModules, sourceDefinitionMap, logger);
}
/**
* Get all compiled module files (valid or invalid) that have previously been written as part of machine source generation.
* @param moduleNames - names of all modules being compiled/loaded
* @param logger
* @return a Map of module name to compiled module source definition.
*/
private final Map<ModuleName, CompiledModuleSourceDefinition> getAllCompiledDefinitions (Set<ModuleName> moduleNames, CompilerMessageLogger logger) {
Map<ModuleName, CompiledModuleSourceDefinition> compiledDefinitionMap = new HashMap<ModuleName, CompiledModuleSourceDefinition>();
if (!isIgnoreCompiledModuleInfo() && !getForceCodeRegen() && !ProgramModifier.IGNORE_COMPILED_MODULE_INFO) {
for (final ModuleName moduleName : moduleNames) {
CompiledModuleSourceDefinition cmsd = getCompiledSourceDefinition(moduleName);
if (cmsd != null) {
compiledDefinitionMap.put(moduleName, cmsd);
}
}
ModuleName[] moduleNamesFromProgram = getProgram().getModuleNames();
for (final ModuleName moduleName : moduleNamesFromProgram) {
CompiledModuleSourceDefinition cmsd = getCompiledSourceDefinition (moduleName);
if (cmsd != null) {
compiledDefinitionMap.put (moduleName, cmsd);
}
}
}
return compiledDefinitionMap;
}
/**
* Helper method for the other compile()'s
* When this method is called, the program should not contain any modules for which module sources are provided.
* @param moduleNames
* @param sourceDefinitionMap (module name to ModuleSourceDefinition) the module source definitions to compile
* @param logger message logger for the compiler
* @return the highest error condition experienced.
*/
private final CompilerMessage.Severity compileSimple (Set<ModuleName> moduleNames, Map<ModuleName, ModuleSourceDefinition> sourceDefinitionMap, CompilerMessageLogger logger) {
// Before we start compiling/loading we should check to see if there are compiled module files
// written out as part of machine source generation.
Map<ModuleName, CompiledModuleSourceDefinition> compiledDefinitionMap = getAllCompiledDefinitions(moduleNames, logger);
compiler.setCompilerMessageLogger(logger);
Packager pk = makePackager(program);
// Setup information for status messages
int nModules = moduleNames.size();
double increment = (Math.round ((1.0 / nModules) * 1000.0))/10.0;
pk.setModuleIncrement(increment);
for (final StatusListener sl : statusListeners) {
pk.addStatusListener(sl);
}
// For the modules to be compiled, remove timestamps before compiling.
// As each module is compiled the packager will update the timestamp to
// the new value.
for (final ModuleName moduleName : moduleNames) {
program.removeCompilationTimestamp(moduleName);
}
CompilerMessage.Severity retVal = compiler.compileModules(moduleNames, sourceDefinitionMap, compiledDefinitionMap, pk, foreignContextProvider);
compiler.setCompilerMessageLogger(null);
return retVal;
}
/**
* @return the program associated with this compiler.
*/
protected final Program getProgram() {
return program;
}
/**
* Remove a named module from this program.
* @param moduleName the module to remove
* @param includeDependents
*/
public final void removeModule(ModuleName moduleName, boolean includeDependents) {
removeModuleInternal (moduleName, includeDependents);
}
/**
* Remove a named module from this program.
* @param moduleName
* @param includeDependents
*/
private final void removeModuleInternal (ModuleName moduleName, boolean includeDependents) {
Set<Module> removedModules = getProgram().removeModule(moduleName, includeDependents);
// Notify change listeners.
for (final Module module : removedModules) {
fireModuleRemoved(module);
}
}
/**
* Add a status listener.
* @param listener StatusListener
*/
public final void addStatusListener(StatusListener listener) {
if (!statusListeners.contains(listener)) {
statusListeners.add(listener);
}
}
/**
* Remove a status listener.
* @param listener StatusListener
*/
public final void removeStatusListener(StatusListener listener) {
statusListeners.remove(listener);
}
/**
* Add a listener to be notified when the program content changes.
* @param listener
*/
public final void addChangeListener (ProgramChangeListener listener){
if (!programChangeListeners.contains (listener)) {
programChangeListeners.add (listener);
}
}
/**
* Remove a listener from the listener list.
* @param listener
*/
public final void removeChangeListener (ProgramChangeListener listener) {
programChangeListeners.remove (listener);
}
/**
* Notify the program change listeners that a module has been loaded.
* @param module the module which was loaded.
*/
private final void fireModuleLoaded(Module module) {
// notify listeners of new module
for (final ProgramChangeListener pcl : programChangeListeners) {
pcl.moduleLoaded (module);
}
}
/**
* Notify the program change listeners that a module has been removed.
* @param module the module which was removed.
*/
private final void fireModuleRemoved(Module module) {
// notify listeners of removed module
for (final ProgramChangeListener pcl : programChangeListeners) {
pcl.moduleRemoved(module);
}
}
/**
* Set the flag which indicates that code regeneration should be forced.
*
* @param b
* the new value for the flag.
*/
public final void setForceCodeRegen (boolean b) {
forceCodeRegen = b;
}
/**
* Get the state of the flag that indicates whether code regeneration should
* be forced.
*
* @return the state of the flag.
*/
public final boolean getForceCodeRegen () {
return forceCodeRegen;
}
/**
* Set the flag indicating whether the compiled code will
* be immediately loaded/run.
*/
public final void setForImmediateUse (boolean b) {
forImmediateUse = b;
}
/**
* Get the flag indicating the compiled code will be immediately loaded/run.
* @return boolean
*/
public boolean isForImmediateUse() {
return forImmediateUse;
}
/**
* @return Returns the ignoreCompiledModuleInfo.
*/
final boolean isIgnoreCompiledModuleInfo() {
return ignoreCompiledModuleInfo;
}
/**
* @param ignoreCompiledModuleInfo The ignoreCompiledModuleInfo to set.
*/
public final void setIgnoreCompiledModuleInfo(boolean ignoreCompiledModuleInfo) {
this.ignoreCompiledModuleInfo = ignoreCompiledModuleInfo;
}
/**
* @param foreignContextProvider the foreign context provider to use, or 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 tooling to provide context not visible to the compiler.
*/
public final void setForeignContextProvider(ForeignContextProvider foreignContextProvider) {
this.foreignContextProvider = foreignContextProvider;
}
/**
* @return the foreign context provider to use, or null to use the default.
*/
final ForeignContextProvider getForeignContextProvider() {
return foreignContextProvider;
}
/**
* @return an iterator over the registered status listeners.
*/
protected final Collection<StatusListener> getStatusListeners () {
return statusListeners;
}
/**
* Use the CAL based optimizer for compiling.
*
*/
public final void useOptimizer(){
compiler.useOptimizer();
}
/**
* This is the ProgramChangeListener interface.
* Interface to be implemented by classes that wish to be notified when the program is modified.
* Listeners can be registered/removed through Program.addChangeListener() and Program.removeChangeListener().
* Created: Mar 14, 2003
* @author RCypher
*/
public interface ProgramChangeListener {
/**
* Notify listeners that a module was loaded.
* This will be called when the module is complete.
* @param moduleName the name of the module.
*/
public void moduleLoaded (Module moduleName);
/**
* Notify listeners that a module was removed from the program.
* @param moduleName the name of the module.
*/
public void moduleRemoved (Module moduleName);
}
}