/*
* 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.
*/
/*
* ValueRunner.java
* Creation date: (Jun 18, 2002 2:49:59 PM)
* By: Edward Lam
*/
package org.openquark.gems.client;
import java.util.ArrayList;
import java.util.logging.Level;
import org.openquark.cal.compiler.AdjunctSource;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.SourceModel;
import org.openquark.cal.compiler.TypeException;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.io.InputPolicy;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.WorkspaceManager;
import org.openquark.cal.valuenode.GemEntityValueNode;
import org.openquark.cal.valuenode.Target;
import org.openquark.cal.valuenode.TargetRunner;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.cal.valuenode.ValueNodeBuilderHelper;
/**
* A value runner runs a CAL definition and returns a corresponding value node.
* This is used to get the value node for some CAL text.
* Note: this runs in immediate mode, and the CAL definition being run should not require extra args to be set.
* Note 2: there's no way to terminate a computation - don't run anything non-terminating!
*
* Some current uses:
* a. running the code examples in the metadata.
* b. loading a value gem from its saved CAL text.
*
* @author Edward Lam
*/
public class ValueRunner extends TargetRunner {
/**
* Constructor for a ValueRunner
* @param workspaceManager the workspace manager on which this runner is based.
*/
public ValueRunner(WorkspaceManager workspaceManager) {
super(workspaceManager);
}
/**
* Execute (in this thread) the supercombinator defined by the Target, and return the resulting value.
* A new execution context will be used.
* @param target Target the target which defines what to run.
* @param targetModule the module in which the target exists.
* @return ValueNode the resulting Value. Null if we can't handle the output, or if evaluation failed.
*/
public ValueNode getValue(Target target, ModuleName targetModule) throws ProgramCompileException {
return getValue(target, new ValueNode[0], targetModule);
}
/**
* Execute (in this thread) the supercombinator defined by the Target, and return the resulting value.
* @param target Target the target which defines what to run.
* @param argValues the valuenode arguments to the target. These must *not* be parametric.
* @param targetModule the module in which the target exists.
* @return ValueNode the resulting Value. null if we can't handle the output, or if evaluation failed.
*/
public ValueNode getValue(Target target, ValueNode[] argValues, ModuleName targetModule) throws ProgramCompileException {
setTarget(target, targetModule);
// The target:
// Note that if we can't get a target type then we return null.
TypeExpr targetGemTypeExpr = getNewTargetTypeExpr();
if (targetGemTypeExpr == null) {
return null;
}
int nArgs = argValues.length;
TypeExpr[] targetTypePieces = targetGemTypeExpr.getTypePieces(nArgs);
// Use the arguments to specialize the target type.
TypeExpr valueNodeArgTypes[] = new TypeExpr[nArgs];
for (int i = 0; i < nArgs; i++) {
valueNodeArgTypes[i] = argValues[i].getTypeExpr();
}
ModuleTypeInfo currentModuleTypeInfo = getWorkspaceManager().getWorkspace().getMetaModule(targetModule).getTypeInfo();
TypeExpr[] specializedTargetTypePieces;
try {
specializedTargetTypePieces = TypeExpr.patternMatchPieces(valueNodeArgTypes, targetTypePieces, currentModuleTypeInfo);
} catch (TypeException te){
// What to do?
GemCutter.CLIENT_LOGGER.log(Level.SEVERE, "Could not determine gem execution output type.");
return null;
}
// The result:
TypeExpr resultType = specializedTargetTypePieces[nArgs];
if (resultType.isFunctionType()) {
// todoFW: HACK! Handle loading function values better.
// The function value will be the last item in the text inside brackets.
// Stip the brackets and get the text which should give us the qualified name
// of the function.
/* old string-based hack
String scDef = getTarget().getTargetDef(null, currentModuleTypeInfo).toString();
int firstEquals = scDef.indexOf(" = ");
scDef = scDef.substring(firstEquals + 3);
scDef = scDef.replaceAll(";|\n", ""); // replace semicolon, newline
if (QualifiedName.isValidCompoundName(scDef)) {
QualifiedName functionName = QualifiedName.makeFromCompoundName(scDef);
GemEntity gemEntity = getWorkspaceManager().getWorkspace().getGemEntity(functionName);
if (gemEntity != null) {
return new GemEntityValueNode(gemEntity, resultType);
}
}
*/
// HACK: assumes that the target definition is of the form:
// private cdInternal_runTarget = Module.function;
// where "Module.function" is the function value in question
AdjunctSource.FromSourceModel scDef = getTarget().getTargetDef(null, currentModuleTypeInfo);
if (scDef.getNElements() == 1) {
SourceModel.TopLevelSourceElement element = scDef.getElement(0);
if (element instanceof SourceModel.FunctionDefn.Algebraic) {
SourceModel.FunctionDefn.Algebraic func =
(SourceModel.FunctionDefn.Algebraic)element;
if (func.getParameters().length == 0) {
SourceModel.Expr definingExpr = func.getDefiningExpr();
if (definingExpr instanceof SourceModel.Expr.Var) {
SourceModel.Expr.Var var = (SourceModel.Expr.Var)definingExpr;
String name = var.toSourceText();
if (QualifiedName.isValidCompoundName(name)) {
QualifiedName functionName = QualifiedName.makeFromCompoundName(name);
GemEntity gemEntity = getWorkspaceManager().getWorkspace().getGemEntity(functionName);
if (gemEntity != null) {
return new GemEntityValueNode(gemEntity, resultType);
}
}
}
}
}
}
// Something went wrong, that means we don't support the type.
return null;
} else {
// Everything else is supported normally.
// Set the typeExpr for the target, the result, and the overloading resolver.
ArrayList<Object> valueList = new ArrayList<Object>();
InputPolicy inputPolicies[] = new InputPolicy[nArgs];
TypeExpr[] argTypes = new TypeExpr[nArgs];
for (int i = 0; i < nArgs; i++) {
Object[] values = argValues[i].getInputJavaValues();
inputPolicies[i] = argValues[i].getInputPolicy();
argTypes[i] = argValues[i].getTypeExpr();
for (int j = 0; j < values.length; ++j) {
valueList.add(values[j]);
}
}
Object[] inputValues = valueList.toArray();
// Build and execute the test program in non-threaded mode.
buildTestProgram(inputPolicies, argTypes);
executeTestProgram(inputValues, false);
// The returned value node won't have a type expression set, so it's up to us to do it.
ValueNode outputValueNode = getOutputValueNode();
if (outputValueNode == null) {
String errorString = "Null value executing text: " + getTarget().getTargetDef(null, currentModuleTypeInfo).toString();
GemCutter.CLIENT_LOGGER.log(Level.WARNING, errorString);
return null;
}
if (error != null) {
String errorString = "Null value executing text: " + getTarget().getTargetDef(null, currentModuleTypeInfo).toString();
GemCutter.CLIENT_LOGGER.log(Level.WARNING, errorString, error);
return null;
}
ValueNode returnNode = outputValueNode.copyValueNode();
returnNode.setOutputJavaValue(executionResult);
return returnNode;
}
}
/**
* Returns whether the target's output type can be handled.
* @param target the target to consider.
* @param targetModule the module in which the target exists.
* @return boolean whether the target's output type can be handled. Note that this will be in the absence
* of any arguments being provided.
*/
private boolean canHandleTargetOutput(Target target, ModuleName targetModule) {
TypeExpr outputTypeExpr = getNewTargetTypeExpr(target, targetModule, getTypeChecker());
return ValueNodeBuilderHelper.canHandleOutput(outputTypeExpr);
}
/**
* Returns the basic error message in string format
* @param target the target.
* @param targetModule the module in which the target exists.
* @return the error message
*/
public String getErrorMessage(Target target, ModuleName targetModule) {
if (canHandleTargetOutput(target, targetModule)) {
String errorMessage = "";
if (error != null) {
errorMessage = error.getMessage();
}
return "Error: " + errorMessage;
} else {
return GemCutter.getResourceString("OutputNotHandled");
}
}
}