/*
* 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.
*/
/*
* ConsoleRunner.java
* Created: Aug 21, 2007
* By: Edward Lam
*/
package org.openquark.cal;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openquark.cal.compiler.AdjunctSource;
import org.openquark.cal.compiler.CodeAnalyser;
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.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.io.EntryPoint;
import org.openquark.cal.compiler.io.EntryPointSpec;
import org.openquark.cal.compiler.io.InputPolicy;
import org.openquark.cal.compiler.io.OutputPolicy;
import org.openquark.cal.internal.module.Cal.Core.CAL_Exception_internal;
import org.openquark.cal.machine.CALExecutor;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.runtime.CALExecutorException;
import org.openquark.cal.services.ProgramModelManager;
/**
* Simple abstract class for clients which have a program in memory
* and would like to compile a run an adjunct against it and output the result to a logger as text.
*
* @author Edward Lam
*/
public abstract class ConsoleRunner {
/** An instance of a Logger for cal messages. */
protected final Logger calLogger;
/** The runtime instance. */
private CALExecutor runtime = null;
private final byte[] runtimeAccessLock = new byte[0]; // used to synchronize access to the runtime field.
private volatile boolean runtimeIsRunning = false;
/**
* @param calLogger the logger connected to the console.
*/
protected ConsoleRunner(Logger calLogger) {
this.calLogger = calLogger;
}
/**
* Terminate execution of the currently running expression if any.
*/
public void terminateExecution() {
synchronized (runtimeAccessLock) {
if (runtimeIsRunning) {
runtime.requestQuit();
}
}
}
/**
* @return the associated programModelManager for querying and compilation
*/
public abstract ProgramModelManager getProgramModelManager();
/** @return whether the source for a module with the given module name is part of the compilable definitions for this runner.
* This includes any sources which have compilation errors and are therefore not part of the ProgramModelManager's program.
* */
public abstract boolean hasModuleSource(ModuleName moduleName);
/**
* Run a cal expression with the given text.
* @param moduleName the name of the module in which to run the expression.
* @param expressionText the text of the expression to run.
* @param qualifyExpressionText if true, an attempt will be made to qualify unqualified symbols in expressionText before it is run.
* False to just run expressionText without attempting qualification.
* @return whether execution terminated normally.
*/
public boolean runExpression(ModuleName moduleName, String expressionText, boolean qualifyExpressionText) {
ModuleTypeInfo moduleTypeInfo = getProgramModelManager().getModuleTypeInfo(moduleName);
if (moduleTypeInfo == null) {
if (hasModuleSource(moduleName)) {
calLogger.severe("Error: Module \"" + moduleName + "\" has compilation errors (or is currently in the process of being compiled).");
} else {
calLogger.severe("Error: Module \"" + moduleName + "\" could not be found.");
}
return false;
}
String maybeQualifiedExpressionText;
if (qualifyExpressionText) {
maybeQualifiedExpressionText = qualifyCodeExpression(moduleName, expressionText);
if (maybeQualifiedExpressionText == null) {
return false;
}
} else {
maybeQualifiedExpressionText = expressionText;
}
// pick a name which doesn't exist in the module.
String targetName = "target";
int index = 1;
while (moduleTypeInfo.getFunctionalAgent(targetName) != null) {
targetName = "target" + index;
index++;
}
EntryPoint entryPoint = compileAdjunct(QualifiedName.make(moduleName, targetName), maybeQualifiedExpressionText);
if (entryPoint == null) {
return false;
}
calLogger.info("Running: " + expressionText);
return runTarget(entryPoint);
}
/**
* Qualify unqualified symbols in the given cal expression.
* @param moduleName the name of the module with respect to which qualification will be performed.
* @param expressionText the text of the cal expression to qualify
* @return the qualified code expression, if qualification was performed successfully. Null if qualification failed.
*/
public String qualifyCodeExpression(ModuleName moduleName, String expressionText) {
// Qualify unqualified symbols in code, if unambiguous
CompilerMessageLogger logger = new MessageLogger();
CodeAnalyser analyser = new CodeAnalyser(getProgramModelManager().getTypeChecker(), getProgramModelManager().getModuleTypeInfo(moduleName), false, false);
CodeAnalyser.QualificationResults qualificationResults = analyser.qualifyExpression(expressionText, null, null, logger, true);
if (qualificationResults == null) {
calLogger.severe("Attempt to qualify expression has failed because of errors: ");
for (final CompilerMessage message : logger.getCompilerMessages()) {
calLogger.info(" " + message.toString());
}
return null;
}
return qualificationResults.getQualifiedCode();
}
/**
* Discard cached CAF results for a given module or the entire program.
* @param moduleName the name of the module for which the cached CAF results will be discarded.
* Null to discard for the entire program.
*/
public void command_resetCachedResults(ModuleName moduleName) {
if (runtime != null) {
if (moduleName == null) {
// Reset cached results in all modules.
calLogger.info("Resetting cached CAF results in program.");
getProgramModelManager().resetCachedResults(runtime.getContext());
} else {
if (getProgramModelManager().getModule(moduleName) == null) {
calLogger.info("Module \"" + moduleName + "\" is not in the program.");
} else {
calLogger.info("Resetting cached CAF results in module " + moduleName + ".");
getProgramModelManager().resetCachedResults(moduleName, runtime.getContext());
}
}
}
}
/**
* @param entryPoint the entry point of the target to run.
* @return whether execution terminated normally.
*/
private boolean runTarget(EntryPoint entryPoint) {
synchronized (runtimeAccessLock) {
if (runtime == null) {
runtime = getProgramModelManager().makeExecutorWithNewContextAndDefaultProperties();
}
runtimeIsRunning = true;
}
long startExec = System.currentTimeMillis();
CALExecutorException error = null;
Object result = null;
try {
result = runtime.exec(entryPoint, new Object[]{});
} catch (CALExecutorException e) {
calLogger.log(Level.SEVERE, "Error while executing.", e);
error = e;
} catch (Exception e) {
calLogger.severe("Unable to execute due to an internal error. Please contact Business Objects. " + e.toString ());
return false;
} catch (Error e){
calLogger.severe("Unable to execute due to an internal error. Please contact Business Objects. " + e.toString ());
return false;
} finally {
synchronized (runtimeAccessLock) {
runtimeIsRunning = true;
}
}
long endExec = System.currentTimeMillis();
calLogger.fine("Finished execution in " + (endExec - startExec) + " ms\n");
showOutput (result, error);
return result != null && error == null;
}
/**
* Display the output results.
* @param result the result object.
* @param error the CALExecutorException if any, or null if none.
*/
private void showOutput(Object result, CALExecutorException error) {
try {
final boolean displayOutput = true;
// Determine the message
if (error == null) {
if (displayOutput) {
if (result == null) {
calLogger.severe("No results to display.");
} else {
calLogger.info("Output:");
calLogger.info(result.toString());
}
}
} else {
String messageTitle;
String message = "No results to display.";
CALExecutorException.Type resultStatus = error.getExceptionType();
if (resultStatus == CALExecutorException.Type.ERROR_FUNCTION_CALL) {
messageTitle = CAL_Prelude.Functions.error.getQualifiedName() + " called:";
message = error.getLocalizedMessage();
} else if (resultStatus == CALExecutorException.Type.PATTERN_MATCH_FAILURE) {
messageTitle = "Error during pattern matching:";
message = error.getLocalizedMessage();
} else if (resultStatus == CALExecutorException.Type.PRIM_THROW_FUNCTION_CALLED) {
messageTitle = CAL_Exception_internal.Functions.primThrow.getQualifiedName() + " called:";
message = error.getLocalizedMessage();
} else if (resultStatus == CALExecutorException.Type.FOREIGN_OR_PRIMITIVE_FUNCTION_EXCEPTION) {
messageTitle = "Error during foreign or primitive function call.";
message = error.getLocalizedMessage();
} else if (resultStatus == CALExecutorException.Type.INTERNAL_RUNTIME_ERROR) {
messageTitle = "Internal runtime error.";
message = error.getLocalizedMessage();
} else if (resultStatus == CALExecutorException.Type.USER_TERMINATED) {
messageTitle = "User terminated.";
} else {
messageTitle = "Unknown result state.";
}
calLogger.severe(messageTitle);
calLogger.severe(message);
}
} catch (NullPointerException e) {
// No results!
calLogger.severe("No results to display.");
}
}
/**
* Create a EntryPoint for the given function definition.
* @param qualifiedTargetName the name of the target (defined in functionDefn) for which the entry point will be generated.
* @param functionBodyText the text of the body of the definition.
* @return a corresponding entry point, or null if there was a problem compiling the definition.
*/
private EntryPoint compileAdjunct(QualifiedName qualifiedTargetName, String functionBodyText) {
String unqualifiedTargetName = qualifiedTargetName.getUnqualifiedName();
String scDef = unqualifiedTargetName + " = \n" + functionBodyText + "\n;";
CompilerMessageLogger ml = new MessageLogger ();
calLogger.fine("Compiling adjunct..");
ModuleName targetModule = qualifiedTargetName.getModuleName();
// Compile it, indicating that this is an adjunct
EntryPoint targetEntryPoint =
getProgramModelManager().getCompiler().getEntryPoint(
new AdjunctSource.FromText(scDef),
EntryPointSpec.make(QualifiedName.make(targetModule, unqualifiedTargetName), new InputPolicy[]{}, OutputPolicy.DEFAULT_OUTPUT_POLICY),
targetModule,
ml);
writeOutCompileResult(ml);
return targetEntryPoint;
}
/**
* A helper method to log the results of compilation to the console logger.
* @param messageLogger the logger containing the messages from compilation.
*/
protected final void writeOutCompileResult(CompilerMessageLogger messageLogger) {
if (messageLogger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
// Errors
calLogger.severe("CAL: Compilation unsuccessful because of errors:");
} else {
// Compilation successful
calLogger.finer("CAL: Compilation successful");
}
for (final CompilerMessage message : messageLogger.getCompilerMessages()) {
Level messageLevel;
if (message.getSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
messageLevel = Level.SEVERE;
} else if (message.getSeverity().compareTo(CompilerMessage.Severity.WARNING) >= 0) {
messageLevel = Level.WARNING;
} else {
messageLevel = Level.FINE;
}
calLogger.log(messageLevel, " " + message.toString());
}
}
}