/**
* Copyright (C) 2001-3, Anthony Harrison anh23@pitt.edu This library is free
* software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details. You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.scripting.condition;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.model.IModel;
import org.jactr.core.production.condition.CannotMatchException;
import org.jactr.core.production.condition.ICondition;
import org.jactr.core.utils.ModelerException;
import org.jactr.scripting.ScopeManager;
import org.jactr.scripting.ScriptSupport;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.WrappedException;
/**
* ScriptableCondition allows for custom actions. You set the script via
* setScript(String ) - it must contain a function matches(model, chunk,
* bindings) { return {true | false}; }
*
* @author harrison
* @created April 18, 2003
*/
public class ScriptableCondition implements ICondition
{
static private transient Log LOGGER = LogFactory
.getLog(ScriptableCondition.class);
/**
* Description of the Field
*/
public final static String DEFAULT_SCRIPT =
"/* match with this custom condition \n*/\n"
+ "function matches()\n"
+ "{\n//insert custom code here\n"
+ "\treturn true; //return true iff the match succeeds\n"
+ "}";
/**
* Description of the Field
*/
protected String _scriptString;
/**
* Description of the Field
*/
protected transient Script _script;
protected boolean _hasFiredCleanly = false;
protected boolean _cleanResult;
/**
* Constructor for the ScriptableCondition object
*/
public ScriptableCondition()
{
this(DEFAULT_SCRIPT);
}
/**
* Constructor for the ScriptableCondition object
*
* @param script
* Description of the Parameter
*/
public ScriptableCondition(String script)
{
setScriptString(script);
}
/**
* Constructor for the ScriptableCondition object
*
* @param scriptString
* Description of the Parameter
* @param script
* Description of the Parameter
*/
protected ScriptableCondition(String scriptString, Script script)
{
_scriptString = scriptString;
setScript(script);
}
public ScriptableCondition clone(IModel model, Map<String, Object> bindings)
throws CannotMatchException
{
return new ScriptableCondition(_scriptString, _script);
}
/**
* Description of the Method
*/
public void dispose()
{
_script = null;
_scriptString = null;
}
/**
* Gets the script attribute of the ScriptableCondition object
*
* @return The script value
*/
public String getScript()
{
return _scriptString;
}
/**
* Sets the script attribute of the ScriptableCondition object
*
* @param str
* The new script value
*/
public void setScriptString(String str)
{
_scriptString = str.trim();
compileScript();
}
private void setScript(Script script)
{
_script = script;
}
/**
* Description of the Method
*/
private void compileScript()
{
ScopeManager.getPublicScope();
Context cx = Context.enter();
try
{
_script = cx.compileString(_scriptString, "ScriptableCondition", 0, null);
}
catch (Exception ioe)
{
Context.exit();
LOGGER.error("Error in scriptable condition", ioe);
throw new ModelerException("Error in Scriptable ICondition", ioe,
"double check your sytanx. The script parser detected an error");
}
}
/**
* ok, this really needs to be rewritten - this is hideous TODO rewrite
*
* @param model
* @param variableBindings
* @throws CannotMatchException
*/
protected boolean execute(IModel model, Map<String, Object> variableBindings)
throws CannotMatchException
{
/*
* we cache the result if it has fired cleanly (no exceptions). This allows
* us to avoid multiple calls during iterative instantiation process where
* bind will be called repeatedly.
*/
if (_hasFiredCleanly) return _cleanResult;
_cleanResult = false;
if (_script == null) compileScript();
// enter the context for the thread and get the shared scope for
// the model..
Context cx = Context.enter();
Scriptable scope = ScopeManager.newScope(ScopeManager
.getScopeForModel(model));
ScopeManager.defineVariable(scope, "jactr", new ScriptSupport(model,
variableBindings));
try
{
// is matches(model, bindings) is already defined, this will
// overwrite it..
_script.exec(cx, scope);
}
catch (WrappedException we)
{
Throwable cause = we.getWrappedException();
if (cause instanceof CannotMatchException)
throw (CannotMatchException) cause;
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
throw new RuntimeException(cause);
}
catch (JavaScriptException jse)
{
Context.exit();
LOGGER.error("Error in scriptable condition", jse);
throw new ModelerException("Error in Scriptable ICondition", jse,
"double check your sytanx in " + variableBindings.get("=production")
+ ". The script was unable to be run..");
}
// let's get the function fire(model, prod, bindings)
Object matches = ScriptableObject.getProperty(scope, "matches");
if (!(matches instanceof Function))
{
Context.exit();
throw new ModelerException("Could not find matches() in script", null,
"ScriptableActions must defined function matches(model, bindings)");
}
// and fire that beatch
boolean matched = false;
try
{
Object[] args = {};
Object result = ((Function) matches).call(cx, scope, scope, args);
try
{
matched = Context.toBoolean(result);
_hasFiredCleanly = true;
_cleanResult = matched;
}
catch (Exception e)
{
matched = false;
}
return matched;
}
catch (WrappedException we)
{
Throwable cause = we.getWrappedException();
if (cause instanceof CannotMatchException)
throw (CannotMatchException) cause;
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
throw new RuntimeException(cause);
}
catch (JavaScriptException jse2)
{
LOGGER.error("Error in scriptable condition", jse2);
throw new ModelerException(
"Error in Scriptable ICondition",
jse2,
"double check your sytanx in "
+ variableBindings.get("=production")
+ ". Scripting failed to execute fire(model, production, bindings)");
}
finally
{
Context.exit();
}
}
// /**
// * @see
// org.jactr.core.production.condition.ICondition#bind(org.jactr.core.model.IModel,
// * java.util.Map)
// */
// public ICondition bind(IModel model, Map<String, Object> variableBindings)
// throws CannotMatchException
// {
// ScriptableCondition sc = new ScriptableCondition(_scriptString, _script);
// if(!sc.execute(model, variableBindings))
// throw new CannotMatchException("Script evaluated to false");
// return sc;
// }
//
public int bind(IModel model, Map<String, Object> variableBindings,
boolean isIterative) throws CannotMatchException
{
try
{
if (!execute(model, variableBindings))
throw new CannotMatchException("Script evaluated to false");
}
catch (CannotMatchException cme)
{
if (!isIterative) throw cme;
return 1;
}
return 0;
}
}