/* ************************************************************************ # # 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.util.ArrayList; import java.util.List; import divconq.lang.op.OperationCallback; import divconq.lang.op.OperationResult; import divconq.xml.XElement; public abstract class BlockInstruction extends Instruction { protected List<Instruction> instructions = new ArrayList<Instruction>(); protected List<Instruction> getInstructions() { return this.instructions; } public void addInstruction(Instruction inst) { this.instructions.add(inst); } @Override public void compile(ActivityManager manager, OperationResult log) { super.compile(manager, log); for (XElement child : this.source.selectAll("*")) { // Internal and Detail are always a special child tag, do not treat these as instructions if ("Internal".equals(child.getName()) || "Detail".equals(child.getName())) continue; Instruction ni = manager.createInstruction(child); if (ni == null) { log.errorTr(509, child.getName()); continue; } ni.setXml(child); this.addInstruction(ni); ni.compile(manager, log); } } // no need for try catch here, should always be handled in the subclass @Override public void run(final StackEntry stack) { if (stack.getState() == ExecuteState.Done) { stack.resume(); return; } if (stack.getState() == ExecuteState.Ready) { stack.setState(ExecuteState.Resume); //stack.resume(); //return; } this.alignInstruction(stack, new OperationCallback() { @Override public void callback() { if (stack.getState() != ExecuteState.Resume) { stack.resume(); return; } final StackBlockEntry bstack = (StackBlockEntry)stack; // give the debugger a more natural feel by pointing to the top instruction before executing it if (bstack.getTopFlag()) { bstack.setTopFlag(false); stack.resume(); return; } bstack.getChild().run(new IInstructionCallback() { @Override public void resume() { StackEntry child = bstack.getChild(); if (child == null) { bstack.setState(ExecuteState.Done); } else { ExecuteState cstate = child.getState(); if ((cstate != ExecuteState.Ready) && (cstate != ExecuteState.Resume)) { if (cstate == ExecuteState.Break) bstack.setState(ExecuteState.Done); else if (cstate == ExecuteState.Continue) BlockInstruction.this.continueInstruction(stack); else { // if Done BlockInstruction.this.nextInstruction(stack, new OperationCallback() { @Override public void callback() { stack.resume(); } }); return; } } } stack.resume(); } }); } }); } // continue is essentially the same as reaching the end of the block // see alignInstruction - subclasses will treat end of block as flag // to either bail or continue the loop (check condition) public void continueInstruction(final StackEntry stack) { final StackBlockEntry bstack = (StackBlockEntry)stack; bstack.setPosition(this.instructions.size()); bstack.setChild(null); bstack.setTopFlag(true); } public void nextInstruction(final StackEntry stack, OperationCallback callback) { final StackBlockEntry bstack = (StackBlockEntry)stack; bstack.setPosition(bstack.getPosition() + 1); // if at end of block, treat as a continue - initiate iteration logic if (bstack.getPosition() >= this.instructions.size()) { this.continueInstruction(stack); callback.complete(); } else this.alignInstruction(stack, callback); } // sub classes will override this method to control instruction placement // and block repetition public void alignInstruction(final StackEntry stack, OperationCallback callback) { StackBlockEntry bstack = (StackBlockEntry)stack; if ((bstack.getState() == ExecuteState.Done) || (bstack.getState() == ExecuteState.Break) || (bstack.getState() == ExecuteState.Continue)) { bstack.setChild(null); } else if (bstack.getPosition() >= this.instructions.size()) { bstack.setChild(null); stack.setState(ExecuteState.Done); } else { Instruction inst = this.instructions.get(bstack.getPosition()); StackEntry child = bstack.getChild(); // literally, is it the same inst? if not then we load the current if ((child == null) || (child.getInstruction() != inst)) bstack.setChild(inst.createStack(stack.getActivity(), bstack)); } callback.complete(); } @Override public StackEntry createStack(Activity act, StackEntry parent) { return new StackBlockEntry(act, parent, this); } @Override public void cancel(StackEntry stack) { // only cancel on current aligned instruction - it is possible to be off here in a race, but instructions // should just be smart and only cancel when running if (stack.getState() == ExecuteState.Resume) { StackEntry child = ((StackBlockEntry)stack).getChild(); if (child != null) child.cancel(); } } }