/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.script;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import org.joda.time.DateTime;
import divconq.hub.Hub;
import divconq.lang.op.FuncResult;
import divconq.lang.op.IOperationObserver;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationObserver;
import divconq.lang.op.OperationResult;
import divconq.struct.Struct;
import divconq.struct.CompositeStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.scalar.BooleanStruct;
import divconq.struct.scalar.DateTimeStruct;
import divconq.struct.scalar.IntegerStruct;
import divconq.struct.scalar.NullStruct;
import divconq.util.IOUtil;
import divconq.util.StringUtil;
import divconq.work.ISmartWork;
import divconq.work.TaskRun;
import divconq.xml.XElement;
import divconq.xml.XmlReader;
// TODO global variables need to be pushed by Collab to all activities, those vars get stored in Activity
// level variables (so be sure to use a good naming convention for globals - all start with "gbl"
// note that Activity, not Main is the root function block, little different but means exit codes are with Activity
public class Activity implements ISmartWork, IInstructionCallback {
protected OperationContext opcontext = null;
protected boolean debugmode = false;
protected boolean inDebugger = false;
protected boolean exitFlag = false;
protected IDebugger debugger = null;
// error handler
protected ErrorMode errorMode = ErrorMode.Resume;
protected long errorCode = 0;
protected String errorMessage = null;
protected Script script = null;
protected StackFunctionEntry stack = null;
protected Instruction inst = null;
protected long starttime = 0;
protected long runtime = 0;
protected AtomicLong runCount = new AtomicLong(); // useful flag to let us know that another instruction has completed
protected AtomicLong varnames = new AtomicLong();
protected Map<String, Struct> globals = new HashMap<String, Struct>();
protected IOperationObserver taskObserver = null; // TODO rework so it is not circular ref
protected boolean hasErrored = false;
public void setContext(OperationContext v) {
this.opcontext = v;
}
public ExecuteState getState() {
return (this.stack != null) ? this.stack.getState() : ExecuteState.Ready;
}
public void setState(ExecuteState v) {
if (this.stack != null)
this.stack.setState(v);
}
public boolean hasErrored() {
return this.hasErrored;
}
public void clearErrored() {
this.hasErrored = false;
}
public Long getExitCode() {
return (this.stack != null) ? this.stack.getLastCode() : 0;
}
public Struct getExitResult() {
return (this.stack != null) ? this.stack.getLastResult() : null;
}
public void setExitFlag(boolean v) {
this.exitFlag = v;
}
public boolean isExitFlag() {
return this.exitFlag;
}
public void setDebugMode(boolean v) {
this.debugmode = v;
}
/*
* has the code signaled that it wants to debug?
*/
public boolean isDebugMode() {
return this.debugmode;
}
public void setInDebugger(boolean v) {
this.inDebugger = v;
}
public void setDebugger(IDebugger v) {
this.debugger = v;
}
public IDebugger getDebugger() {
return this.debugger;
}
public void setErrorMode(ErrorMode errorMode, long errorCode, String errorMessage) {
this.errorMode = errorMode;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
/*
* is the code already managed by a debugger
*/
public boolean isInDebugger() {
return this.inDebugger;
}
public String getTitle() {
if (this.script == null)
return null;
return this.script.getTitle();
}
public long getRuntime() {
return this.runtime;
}
public long getRunCount() {
return this.runCount.get();
}
@Override
public void run(TaskRun scriptrun) {
if (this.opcontext == null) {
this.opcontext = scriptrun.getContext();
this.taskObserver = new OperationObserver() {
// lock is too expensive, this is a flag just too keep us from calling ourself again, not for thread safety
// worse case is we get a few more log messages than we wanted, should be very rare (task killed same time script makes error)
protected boolean inHandler = false;
@Override
public void log(OperationContext ctx, RecordStruct entry) {
if ("Error".equals(entry.getFieldAsString("Level"))) {
if (this.inHandler)
return;
this.inHandler = true;
try {
Activity.this.hasErrored = true;
long lcode = entry.getFieldAsInteger("Code", 1);
if (lcode > 0) {
StackEntry se = Activity.this.stack.getExecutingStack();
if (se != null)
se.setLastCode(lcode);
}
if (StringUtil.isNotEmpty(Activity.this.errorMessage) && (Activity.this.errorCode > 0))
ctx.exit(Activity.this.errorCode, Activity.this.errorMessage);
else if (StringUtil.isNotEmpty(Activity.this.errorMessage))
ctx.exit(1, Activity.this.errorMessage);
else if (Activity.this.errorCode > 0)
ctx.exitTr(Activity.this.errorCode);
if (Activity.this.errorMode == ErrorMode.Debug)
Activity.this.engageDebugger();
else if (Activity.this.errorMode == ErrorMode.Exit)
Activity.this.setExitFlag(true);
// else if resume do nothing
}
finally {
this.inHandler = false;
}
}
}
};
this.opcontext.addObserver(this.taskObserver);
}
if (this.inst == null) {
this.exitFlag = true;
}
else if (this.stack == null) {
this.starttime = System.currentTimeMillis();
this.stack = (StackFunctionEntry)this.inst.createStack(this, null);
this.stack.setParameter(scriptrun.getTask().getParams());
}
if (this.exitFlag) {
scriptrun.complete();
return;
}
this.stack.run(this);
}
@Override
public void resume() {
TaskRun run = this.opcontext.getTaskRun();
if (run == null) {
System.out.println("Resume with no run!!!");
return;
}
if (this.exitFlag)
run.complete();
else if (this.debugmode) {
IDebugger d = this.debugger;
if (d != null)
d.stepped();
}
else
Hub.instance.getWorkPool().submit(run);
this.runCount.incrementAndGet();
}
@Override
public void cancel(TaskRun scriptrun) {
scriptrun.error("script task run canceled");
if (this.stack != null)
this.stack.cancel();
System.out.println("activity canceled");
}
@Override
public void completed(TaskRun scriptrun) {
this.runtime = (System.currentTimeMillis() - this.starttime);
}
public void engageDebugger() {
this.opcontext.debug("Debugger requested");
this.debugmode = true;
if (this.inDebugger)
return;
// need a task run to do debugging
if (this.opcontext.getTaskRun() != null) {
IDebuggerHandler debugger = Hub.instance.getActivityManager().getDebugger();
if (debugger == null) {
this.opcontext.error("Unable to debug script, no debugger registered.");
this.opcontext.getTaskRun().kill();
}
else {
// so debugging don't timeout
this.opcontext.getTaskRun().getTask().withTimeout(0).withDeadline(0);
debugger.startDebugger(this.opcontext.getTaskRun());
}
}
}
public Struct createStruct(String type) {
if (this.opcontext.getTaskRun() != null)
return Hub.instance.getActivityManager().createVariable(type);
return NullStruct.instance;
}
public RecordStruct getDebugInfo() {
RecordStruct info = new RecordStruct(); // TODO type this
if (this.opcontext != null)
info.setField("Log", this.opcontext.getMessages());
ListStruct list = new ListStruct();
// global level
RecordStruct dumpRec = new RecordStruct();
list.addItem(dumpRec);
dumpRec.setField("Line", 1);
dumpRec.setField("Column", 1);
dumpRec.setField("Command", ">Global<");
RecordStruct dumpVariables = new RecordStruct();
dumpRec.setField("Variables", dumpVariables);
for (Entry<String, Struct> var : this.globals.entrySet())
dumpVariables.setField(var.getKey(), var.getValue());
dumpVariables.setField("_Errored", new BooleanStruct(this.hasErrored));
if (this.opcontext != null)
dumpVariables.setField("_ExitCode", new IntegerStruct(this.opcontext.getCode()));
// add the rest of the stack
if (this.stack != null)
this.stack.debugStack(list);
info.setField("Stack", list);
return info;
}
public OperationResult compile(String source) {
OperationResult res = new OperationResult();
boolean checkmatches = true;
Set<String> includeonce = new HashSet<>();
while (checkmatches) {
checkmatches = false;
Matcher m = Script.includepattern.matcher(source);
while (m.find()) {
String grp = m.group();
String path = grp.trim();
path = path.substring(10, path.length() - 3);
String lib = "\n";
if (! includeonce.contains(path)) {
System.out.println("Including: " + path);
// set lib from file content
lib = IOUtil.readEntireFile(new File("." + path));
if (StringUtil.isEmpty(lib))
lib = "\n";
else
lib = "\n" + lib;
includeonce.add(path);
}
source = source.replace(grp, lib);
checkmatches = true;
}
}
FuncResult<XElement> xres = XmlReader.parse(source, true);
if (res.hasErrors()) {
res.error("Unable to parse script");
return res;
}
this.script = new Script();
this.script.compile(xres.getResult(), source);
if (res.hasErrors()) {
res.error("Unable to compile script");
return res;
}
this.inst = this.script.getMain();
return res;
}
public Script getScript() {
return this.script;
}
// global variables
public Struct queryVariable(String name) {
if (StringUtil.isEmpty(name))
return null;
if ("_Errored".equals(name))
return new BooleanStruct(this.hasErrored);
if ("_ExitCode".equals(name))
return new IntegerStruct(this.opcontext.getCode());
if ("_Log".equals(name))
return this.opcontext.getMessages();
if ("_Now".equals(name))
return new DateTimeStruct(new DateTime());
// do not call super - that would expose vars outside of the function
int dotpos = name.indexOf(".");
if (dotpos > -1) {
String oname = name.substring(0, dotpos);
Struct ov = this.globals.containsKey(oname) ? this.globals.get(oname) : null;
if (ov == null) {
this.opcontext.errorTr(507, oname);
return null;
}
if (!(ov instanceof CompositeStruct)){
this.opcontext.errorTr(508, oname);
return null;
}
FuncResult<Struct> sres = ((CompositeStruct)ov).select(name.substring(dotpos + 1));
return sres.getResult();
}
else if (this.globals.containsKey(name)) {
return this.globals.get(name);
}
return null;
}
public Instruction queryFunction(String name) {
return this.script.getFunction(name);
}
public void addVariable(String name, Struct var) {
this.globals.put(name, var);
}
public String tempVarName() {
return this.varnames.incrementAndGet() + "";
}
}