Java tutorial
/******************************************************************************* ** ** Advanced Distributed Learning Co-Laboratory (ADL Co-Lab) Hub grants you ** ("Licensee") a non-exclusive, royalty free, license to use, modify and ** redistribute this software in source and binary code form, provided that ** i) this copyright notice and license appear on all copies of the software; ** and ii) Licensee does not utilize the software in a manner which is ** disparaging to ADL Co-Lab Hub. ** ** This software is provided "AS IS," without a warranty of any kind. ALL ** EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ** ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE ** OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. ADL Co-Lab Hub AND ITS LICENSORS ** SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF ** USING, MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO ** EVENT WILL ADL Co-Lab Hub OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, ** PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, ** INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE ** THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE ** SOFTWARE, EVEN IF ADL Co-Lab Hub HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH ** DAMAGES. ** *******************************************************************************/ package org.adl.sequencer.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map.Entry; import java.util.Random; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import org.adl.sequencer.ADLAuxiliaryResource; import org.adl.sequencer.ADLDuration; import org.adl.sequencer.ADLObjStatus; import org.adl.sequencer.ADLTracking; import org.adl.sequencer.ADLValidRequests; import org.adl.sequencer.ActivityNode; import org.adl.sequencer.IDuration; import org.adl.sequencer.ISeqActivity; import org.adl.sequencer.ISeqActivityTree; import org.adl.sequencer.ISeqRollupRuleset; import org.adl.sequencer.ISeqRuleset; import org.adl.sequencer.ISequencer; import org.adl.sequencer.IValidRequests; import org.adl.sequencer.SeqActivity; import org.adl.sequencer.SeqActivityTree; import org.adl.sequencer.SeqNavRequests; import org.adl.sequencer.SeqRollupRuleset; import org.adl.sequencer.SeqRule; import org.adl.sequencer.SeqRuleset; import org.adl.util.debug.DebugIndicator; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Reference sequencing implementation of the IMS Simple Sequencing * Specification.<br> * <br> * * <strong>Filename:</strong> ADLSequencer.java<br> * <br> * * <strong>Description:</strong><br> * The <code>ADLSequencer</code> encapsulates the functionality of all four * conceptual processes required for sequencing: navigation interpreter, * sequencing, rollup, and delivery.<br> * <br> * * The approach taken with this implementation is to provide public interfaces * for the RTE, enabling navigation/delivery, status reporting, simple session * management, and TOC information.<br> * <br> * * Internaly, the <code>ADLSequencer</code> acts on a * <code>SeqActivityTree</code>, which provides state management of the * activity tree and access to the internal structures of its activities.<br> * <br> * * <strong>Design Issues:</strong><br> * This implementation is intended to be used by the SCORM Sample RTE 2.0.<br> * <br> * * <strong>Implementation Issues:</strong><br> * This implementation encapsulates all conceptual sequencing behaviors and * their corresponding processes. This implementation processes navigation * requests serially.<br> * <br> * * To ensure the activity tree remains in a consistent state, the success of * requests are tracked globally by the sequencer.<br> * <br> * * This implementation has not been optimized.<br> * <br> * * <strong>Known Problems:</strong><br> * <br> * * <strong>Side Effects:</strong><br> * <br> * * <strong>References:</strong><br> * <ul> * <li>IMS Simple Sequencing 1.0 * <li>SCORM 2004 * </ul> * * @author ADL Technical Team */ public class ADLSequencer implements SeqNavigation, SeqReportActivityStatus, ISequencer { /** * Private class Required by various traversal methods to provide an 'out' * parameter */ private class Walk { /** * A sequencing activity */ public SeqActivity at = null; /** * Used to indcate the direction of the walk. The default value is no * flow */ public int direction = ADLSequencer.FLOW_NONE; /** * Used to describe if the flow traversal walked off the acivity tree */ public boolean endSession = false; /** * Default constructor for the private class */ public Walk() { // Default constructor } } /** * ADLObjStatus */ private static final long serialVersionUID = 2693585400168148602L; private static Log log = LogFactory.getLog(ADLSequencer.class); /** * Enumeration of the traversal directions -- described in section SB.4 of * the IMS SS Specification. <br> * None <br> * <b>0</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static final int FLOW_NONE = 0; /** * Enumeration of the traversal directions -- described in section SB.4 of * the IMS SS Specification. <br> * Forward <br> * <b>1</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static final int FLOW_FORWARD = 1; /** * Enumeration of the traversal directions -- described in section SB.4 of * the IMS SS Specification. <br> * Backward <br> * <b>1</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static final int FLOW_BACKWARD = 2; /** * This controls display of log messages to the java console. */ private static boolean _Debug = DebugIndicator.ON; /** * Enumeration of the possible termination requests -- described in section * TB of the IMS SS Specification. <br> * Exit <br> * <b>"_EXIT_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String TER_EXIT = "_EXIT_"; /** * Enumeration of the possible termination requests -- described in section * TB of the IMS SS Specification. <br> * Exit All <br> * <b>"_EXITALL_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String TER_EXITALL = "_EXITALL_"; /** * Enumeration of the possible termination requests -- described in section * TB of the IMS SS Specification. <br> * Supsend All <br> * <b>"_SUSPENDALL_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String TER_SUSPENDALL = "_SUSPENDALL_"; /** * Enumeration of the possible termination requests -- described in section * TB of the IMS SS Specification. <br> * Abandon <br> * <b>"_ABANDON_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String TER_ABANDON = "_ABANDON_"; /** * Enumeration of the possible termination requests -- described in section * TB of the IMS SS Specification. <br> * Abandon All <br> * <b>"_ABANDONALL_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String TER_ABANDONALL = "_ABANDONALL_"; /** * Enumeration of the possible sequencing requests -- described in section * SB of the IMS SS Specification. <br> * Start <br> * <b>"_START_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String SEQ_START = "_START_"; /** * Enumeration of the possible sequencing requests -- described in section * SB of the IMS SS Specification. <br> * Retry <br> * <b>"_RETRY_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String SEQ_RETRY = "_RETRY_"; /** * Enumeration of the possible sequencing requests -- described in section * SB of the IMS SS Specification. <br> * Resume All <br> * <b>"_RESUMEALL_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String SEQ_RESUMEALL = "_RESUMEALL_"; /** * Enumeration of the possible sequencing requests -- described in section * SB of the IMS SS Specification. <br> * Exit <br> * <b>"_EXIT_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String SEQ_EXIT = "_EXIT_"; /** * Enumeration of the possible sequencing requests -- described in section * SB of the IMS SS Specification. <br> * Continue <br> * <b>"_CONTINUE_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String SEQ_CONTINUE = "_CONTINUE_"; /** * Enumeration of the possible sequencing requests -- described in section * SB of the IMS SS Specification. <br> * Previous <br> * <b>"_PREVIOUS_"</b> <br> * [SEQUENCING SUBSYSTEM CONSTANT] */ private static String SEQ_PREVIOUS = "_PREVIOUS_"; /** * Internal activity tree this instance of the sequencer acts upon. */ private SeqActivityTree mSeqTree = null; /** * Indicates if the sequencing session has ended. */ private boolean mEndSession = false; /** * Indicates that an exit action rule was successfully evaluated at the root * of the activity tree. */ private boolean mExitCourse = false; /** * Indicates if the sequencer is currently processing a retry sequencing * request. */ private boolean mRetry = false; /** * Indicates if an exitAll was tentativly processed. */ private boolean mExitAll = false; // The following attributes track the global state of the overall sequencing // process. /** * Indicates if the most recently processed termination request was valid. */ private boolean mValidTermination = true; /** * Indicates if the most recently processed sequencing request was valid. */ private boolean mValidSequencing = true; /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Public Methods -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * This method returns the current (as of the function call) valid Table of * Contents (TOC) for the activity tree. The format of the resulting List * is a result of a breadth-first walk of the activity tree.<br> * <br> * * @param iStart * The 'root' of the requested TOC. * * @return A List of <code>ADLTOC</code> objects describing the current. */ /*private List getTOC(SeqActivity iStart) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - getTOC"); } List toc = new ArrayList(); ADLTOC temp = null; boolean done = false; // Make sure we have an activity tree if (mSeqTree == null) { if (_Debug) { System.out.println(" ::--> No Activity Tree"); } done = true; } // Perform a breadth-first walk of the activity tree. SeqActivity walk = iStart; int depth = 0; int parentTOC = -1; List lookAt = new ArrayList(); List flatTOC = new ArrayList(); // Tree traversal status indicators boolean next = false; boolean include = false; boolean collapse = false; // Make sure the activity has been associated with this sequencer // If not, build the TOC from the root if (walk == null) { walk = mSeqTree.getRoot(); } if (!done) { if (_Debug) { System.out.println(" ::--> Building TOC from: " + walk.getID()); } } SeqActivity cur = mSeqTree.getFirstCandidate(); int curIdx = -1; if (cur == null) { cur = mSeqTree.getCurrentActivity(); } while (!done) { include = false; collapse = false; next = false; // If the activity is a valid target for a choice sequecing request, // include it in the TOC and determine its attributes if (walk.getParent() != null) { if (walk.getParent().getControlModeChoice()) { include = true; } } else { // Always include the root of the activity tree in the TOC include = true; } // Make sure the activity we are considering is not disabled or // hidden if (include) { // Attempt to get rule information from the activity ISeqRuleset hiddenRules = walk.getPreSeqRules(); String result = null; if (hiddenRules != null) { result = hiddenRules.evaluate(SeqRuleset.RULE_TYPE_HIDDEN, walk, false); } // If the rule evaluation did not return null, the activity // must be hidden. if (result != null) { if (_Debug) { System.out.println(" ::--> HIDDEN"); } include = false; collapse = true; } else { // Check if this activity is prevented from activation if (walk.getPreventActivation() && !walk.getIsActive()) { if (cur != null) { if (walk != cur && cur.getParent() != walk) { if (_Debug) { System.out.println(" ::--> PREVENTED !!"); System.out.println(" " + walk.getID() + " != " + cur.getParent().getID()); } include = false; } } else { if (_Debug) { System.out .println(" ::--> PREVENTED -- no cur"); } include = false; } } } } // The activity is included in the TOC, set its attributes if (include) { ISeqActivity parent = walk.getParent(); temp = new ADLTOC(); temp.mCount = walk.getCount(); temp.mTitle = walk.getTitle(); temp.mDepth = depth; temp.mIsVisible = walk.getIsVisible(); temp.mIsEnabled = !checkActivity(walk); temp.mID = walk.getID(); if (walk.getParent() != null) { temp.mInChoice = walk.getParent().getControlModeChoice(); } else { temp.mInChoice = true; } // Check if we looking at the 'current' cluster if (cur != null) { if (temp.mID.equals(cur.getID())) { temp.mIsCurrent = true; curIdx = toc.size(); } } temp.mLeaf = !walk.hasChildren(false); temp.mParent = parentTOC; if (_Debug) { System.out.println(" :: Added :: " + temp.mID + " [[ " + temp.mDepth + " ]] (" + temp.mParent + ") // " + temp.mIsSelectable + " [E] " + temp.mIsEnabled); } toc.add(temp); } else { temp = new ADLTOC(); temp.mCount = walk.getCount(); temp.mTitle = walk.getTitle(); temp.mIsVisible = walk.getIsVisible(); temp.mIsEnabled = !checkActivity(walk); temp.mDepth = -(depth); temp.mID = walk.getID(); temp.mIsSelectable = false; temp.mLeaf = (walk.getChildren(false) == null); temp.mParent = parentTOC; if (collapse) { temp.mIsVisible = false; } if (_Debug) { System.out.println(" :: Added not-included :: " + temp.mID + " [[ " + temp.mDepth + " ]] (" + temp.mParent + ") // " + temp.mIsSelectable + " [E] " + temp.mIsEnabled); } toc.add(temp); } // Add this activity to the "flat TOC" flatTOC.add(walk); // If this activity has children, look at them later... if (walk.hasChildren(false)) { // Remember where we are at and look at the children now, // unless we are at the root if (walk.getParent() != null) { lookAt.add(walk); } // Go to the first child walk = (SeqActivity) (walk.getChildren(false)).get(0); parentTOC = toc.size() - 1; depth++; next = true; } if (!next) { // Move to its sibling walk = walk.getNextSibling(false); temp = (ADLTOC) toc.get(toc.size() - 1); parentTOC = temp.mParent; while (walk == null && !done) { if (lookAt.size() > 0) { // Walk back up the tree to the parent's next sibling walk = (SeqActivity) lookAt .get(lookAt.size() - 1); lookAt.remove(lookAt.size() - 1); depth--; // Find the correct parent temp = (ADLTOC) toc.get(parentTOC); while (!temp.mID.equals(walk.getID())) { parentTOC = temp.mParent; temp = (ADLTOC) toc.get(parentTOC); } walk = walk.getNextSibling(false); } else { done = true; } } if (walk != null) { parentTOC = temp.mParent; } } } if (_Debug) { System.out.println(" ::--> Completed first pass"); } // After the TOC has been created, mark activites unselectable // if the Prevent Activation prevents them being selected, // and mark them invisible if they are descendents of a hidden // from choice activity int hidden = -1; int prevented = -1; for (int i = 0; i < toc.size(); i++) { SeqActivity tempAct = (SeqActivity) flatTOC.get(i); ADLTOC tempTOC = (ADLTOC) toc.get(i); if (_Debug) { System.out.println(" ::--> Evaluating --> " + tempAct.getID()); System.out.println(" --> " + tempTOC.mTitle); } int checkDepth = ((tempTOC.mDepth >= 0) ? tempTOC.mDepth : (-tempTOC.mDepth)); if (hidden != -1) { // Check to see if we are done hiding activities if (checkDepth <= hidden) { hidden = -1; } else { // This must be a descendent tempTOC.mDepth = -(depth); tempTOC.mIsSelectable = false; tempTOC.mIsVisible = false; } } // Evaluate hide from choice rules if we are not hidden if (hidden == -1) { // Attempt to get rule information from the activity ISeqRuleset hiddenRules = tempAct.getPreSeqRules(); String result = null; if (hiddenRules != null) { result = hiddenRules.evaluate(SeqRuleset.RULE_TYPE_HIDDEN, tempAct, false); } // If the rule evaluation did not return null, the activity // must be hidden. if (result != null) { if (_Debug) { System.out.println(" ::--> Hidden --> " + tempTOC.mDepth); } // The depth we are looking for should be positive hidden = -tempTOC.mDepth; prevented = -1; } else { if (_Debug) { System.out.println(" ::--> Prevented ??" + prevented); } if (prevented != -1) { // Check to see if we are done preventing activities if (checkDepth <= prevented) { // Reset the check until we find another prevented prevented = -1; } else { // This must be a prevented descendent tempTOC.mDepth = -1; tempTOC.mIsSelectable = false; } } else { // Check if this activity is prevented from activation if (tempAct.getPreventActivation() && !tempAct.getIsActive()) { if (cur != null) { if (tempAct != cur && cur.getParent() != tempAct) { if (_Debug) { System.out .println(" ::--> PREVENTED !!"); System.out.println(" " + tempAct.getID() + " != " + cur.getParent().getID()); } include = false; prevented = (tempTOC.mDepth > 0) ? tempTOC.mDepth : -tempTOC.mDepth; // The activity cannot be selected temp.mDepth = -1; temp.mIsSelectable = false; } } } } } } } if (_Debug) { System.out.println(" ::--> Completed post-1 pass"); } // After the TOC has been created, mark activites unselectable // if the Choice Exit control prevents them being selected SeqActivity noExit = null; if (mSeqTree.getFirstCandidate() != null) { walk = mSeqTree.getFirstCandidate().getParent(); } else { walk = null; } // Walk up the active path looking for a non-exiting cluster while (walk != null && noExit == null) { // We cannot choose any target that is outside of the activiy tree, // so choice exit does not apply to the root of the tree if (walk.getParent() != null) { if (!walk.getControlModeChoiceExit()) { noExit = walk; } } // Move up the tree walk = walk.getParent(); } if (noExit != null) { depth = -1; if (_Debug) { System.out.println(" ::--> Found NoExit Cluster -- " + noExit.getID()); } // Only descendents of this activity can be selected. for (int i = 0; i < toc.size(); i++) { temp = (ADLTOC) toc.get(i); // When we find the the 'non-exiting' activity, remember its // depth if (temp.mID.equals(noExit.getID())) { depth = (temp.mDepth > 0) ? temp.mDepth : -temp.mDepth; // The cluster activity cannot be selected temp.mDepth = -1; temp.mIsSelectable = false; } // If we haven't found the the 'non-exiting' activity yet, then // the // activity being considered cannot be selected. else if (depth == -1) { temp.mDepth = -1; temp.mIsSelectable = false; } // When we back out of the depth-first-walk and encounter a // sibling // or parent of the 'non-exiting' activity, start making // activity // unselectable else if (((temp.mDepth > 0) ? temp.mDepth : -temp.mDepth) <= depth) { depth = -1; temp.mDepth = -1; temp.mIsSelectable = false; } } } // Boundary Condition -- evaluate choice exit on root temp = (ADLTOC) toc.get(0); SeqActivity root = mSeqTree.getRoot(); if (!root.getControlModeChoiceExit()) { temp.mIsSelectable = false; } if (_Debug) { System.out.println(" ::--> Completed second pass"); } // Look for constrained activities relative to the current activity and // mark activites unselectable if they are outside of the avaliable set SeqActivity con = null; if (mSeqTree.getFirstCandidate() != null) { walk = mSeqTree.getFirstCandidate().getParent(); } else { walk = null; } // Walk up the tree to the root while (walk != null && con == null) { if (walk.getConstrainChoice()) { con = walk; } walk = walk.getParent(); } // Evaluate constrained choice set if (con != null) { if (_Debug) { System.out .println(" ::--> Constrained Choice Activity Found"); System.out.println(" ::--> Stopped at --> " + con.getID()); } int forwardAct = -1; int backwardAct = -1; List list = null; Walk walkCon = new Walk(); walkCon.at = con; // Find the next activity relative to the constrained activity. processFlow(FLOW_FORWARD, false, walkCon, true); if (walkCon.at == null) { if (_Debug) { System.out.println(" ::--> Walked forward off the tree"); } walkCon.at = con; } String lookFor = ""; list = walkCon.at.getChildren(false); if (list != null) { int size = list.size(); lookFor = ((SeqActivity) list.get(size - 1)).getID(); } else { lookFor = walkCon.at.getID(); } for (int j = 0; j < toc.size(); j++) { temp = (ADLTOC) toc.get(j); if (temp.mID.equals(lookFor)) { forwardAct = j; break; } } // Find the previous activity relative to the constrained activity. walkCon.at = con; processFlow(FLOW_BACKWARD, false, walkCon, true); if (walkCon.at == null) { if (_Debug) { System.out.println(" ::--> Walked backward off the tree"); } walkCon.at = con; } lookFor = walkCon.at.getID(); for (int j = 0; j < toc.size(); j++) { temp = (ADLTOC) toc.get(j); if (temp.mID.equals(lookFor)) { backwardAct = j; break; } } // If the forward activity on either end of the range is a cluster, // we need to include its descendents temp = (ADLTOC) toc.get(forwardAct); if (!temp.mLeaf) { int idx = forwardAct; boolean foundLeaf = false; while (!foundLeaf) { for (int i = toc.size() - 1; i > idx; i--) { temp = (ADLTOC) toc.get(i); if (temp.mParent == idx) { idx = i; foundLeaf = temp.mLeaf; break; } } } if (idx != toc.size()) { forwardAct = idx; } } if (_Debug) { System.out.println(" ::--> Constrained Range == [ " + backwardAct + " , " + forwardAct + " ]"); } // Disable activities outside of the avaliable range for (int i = 0; i < toc.size(); i++) { temp = (ADLTOC) toc.get(i); if (i < backwardAct || i > forwardAct) { temp.mIsSelectable = false; if (_Debug) { System.out.println(" ::--> Turn off -- " + temp.mID); } } } } if (_Debug) { System.out.println(" ::--> Completed third pass"); } // Walk the TOC looking for disabled activities... if (toc != null) { depth = -1; for (int i = 0; i < toc.size(); i++) { temp = (ADLTOC) toc.get(i); if (depth != -1) { if (depth >= ((temp.mDepth > 0) ? temp.mDepth : -temp.mDepth)) { depth = -1; } else { temp.mIsEnabled = false; temp.mIsSelectable = false; } } if (!temp.mIsEnabled && depth == -1) { // Remember where the disabled activity is depth = (temp.mDepth > 0) ? temp.mDepth : -temp.mDepth; if (_Debug) { System.out.println(" ::--> [" + i + "] " + "Found Disabled --> " + temp.mID + " <<" + temp.mDepth + ">>"); } } } } if (_Debug) { System.out.println(" ::--> Completed fourth pass"); } // If there is a current activity, check availablity of its siblings // This pass corresponds to Case #2 of the Choice Sequencing Request if (toc != null && curIdx != -1) { if (_Debug) { System.out .println(" ::--> Checking Current Activity Siblings"); } int par = ((ADLTOC) toc.get(curIdx)).mParent; int idx; // Check if the current activity is in a forward only cluster if (cur.getParent() != null && cur.getParent().getControlForwardOnly()) { idx = curIdx - 1; temp = (ADLTOC) toc.get(idx); while (temp.mParent == par) { temp.mIsSelectable = false; idx--; temp = (ADLTOC) toc.get(idx); } } // Check for Stop Forward Traversal Rules idx = curIdx; boolean blocked = false; while (idx < toc.size()) { temp = (ADLTOC) toc.get(idx); if (temp.mParent == par) { if (!blocked) { ISeqRuleset stopTrav = getActivity(temp.mID) .getPreSeqRules(); String result = null; if (stopTrav != null) { result = stopTrav.evaluate( SeqRuleset.RULE_TYPE_FORWARDBLOCK, getActivity(temp.mID), false); } // If the rule evaluation did not return null, the // activity is blocked blocked = (result != null); } else { temp.mIsSelectable = false; } } idx++; } } if (_Debug) { System.out.println(" ::--> Completed fifth pass"); } // Evaluate Stop Forward Traversal Rules -- this pass cooresponds to // Case #3 and #5 of the Choice Sequencing Request Subprocess. In these // cases, we need to check if the target activity is forward in the // Activity Tree relative to the commen ancestor and cuurent activity if (toc != null && curIdx != -1) { if (_Debug) { System.out.println(" ::--> Checking Stop Forward Traversal"); } int curParent = ((ADLTOC) toc.get(curIdx)).mParent; int idx = toc.size() - 1; temp = (ADLTOC) toc.get(idx); // Walk backward from last available activity, // checking each until we get to a sibling of the current activity while (temp.mParent != -1 && temp.mParent != curParent) { temp = (ADLTOC) toc.get(temp.mParent); ISeqRuleset stopTrav = getActivity(temp.mID).getPreSeqRules(); String result = null; if (stopTrav != null) { result = stopTrav.evaluate( SeqRuleset.RULE_TYPE_FORWARDBLOCK, getActivity(temp.mID), false); } // If the rule evaluation did not return null, // then all of its descendents are blocked if (result != null) { if (_Debug) { System.out.println(" ::--> BLOCKED SOURCE --> " + temp.mID + " [" + temp.mDepth + "]"); } // The depth of the blocked activity int blocked = temp.mDepth; for (int i = idx; i < toc.size(); i++) { ADLTOC tempTOC = (ADLTOC) toc.get(i); int checkDepth = ((tempTOC.mDepth >= 0) ? tempTOC.mDepth : (-tempTOC.mDepth)); // Check to see if we are done blocking activities if (checkDepth <= blocked) { break; } // This activity must be a descendent tempTOC.mIsSelectable = false; } } idx--; temp = (ADLTOC) toc.get(idx); } } if (_Debug) { System.out.println(" ::--> Completed sixth pass"); } // Boundary condition -- if there is a TOC make sure all "selectable" // clusters actually flow into content for (int i = 0; i < toc.size(); i++) { temp = (ADLTOC) toc.get(i); if (!temp.mLeaf) { if (_Debug) { System.out .println(" ::--> Process 'Continue' request from " + temp.mID); } SeqActivity from = getActivity(temp.mID); // Confirm 'flow' is enabled from this cluster if (from.getControlModeFlow()) { // Begin traversing the activity tree from the root Walk treeWalk = new Walk(); treeWalk.at = from; boolean success = processFlow(ADLSequencer.FLOW_FORWARD, true, treeWalk, false); if (!success) { temp.mIsSelectable = false; if (_Debug) { System.out .println(" :+: CONTINUE FAILED :+: --> " + treeWalk.at.getID()); } } } else { // Cluster does not have flow == true temp.mIsSelectable = false; } } } if (_Debug) { System.out.println(" ::--> Completed seventh pass"); } for (int i = toc.size() - 1; i >= 0; i--) { temp = (ADLTOC) toc.get(i); if (temp.mIsCurrent && temp.mInChoice) { if (temp.mDepth < 0) { temp.mDepth = -temp.mDepth; } } if (temp.mDepth >= 0) { while (temp.mParent != -1) { temp = (ADLTOC) toc.get(temp.mParent); if (temp.mDepth < 0) { temp.mDepth = -temp.mDepth; } } } else if (temp.mIsVisible) { temp.mDepth = -1; } } for (int i = 0; i < toc.size(); i++) { temp = (ADLTOC) toc.get(i); if (!temp.mIsVisible) { temp.mDepth = -1; List parents = new ArrayList(); for (int j = i + 1; j < toc.size(); j++) { temp = (ADLTOC) toc.get(j); if (temp.mParent == i && temp.mDepth > 0) { temp.mDepth--; parents.add(Integer.valueOf(j)); } else { if (temp.mDepth != -1) { int idx = parents .indexOf(Integer.valueOf(temp.mParent)); if (idx != -1) { temp.mDepth--; parents.add(Integer.valueOf(j)); } } } } } } if (_Debug) { System.out.println(" ::--> Completed TOC walk up"); } if (_Debug) { System.out.println(" :: ADLSequencer --> END - getTOC"); } return toc; }*/ private boolean canBeIncluded(SeqActivity walk, SeqActivity cur, ActivityNode node) { // If we're _not_ looking at the root node, and its parent is _not_ a choice event, then quit now. if (walk.getParent() != null && !walk.getParent().getControlModeChoice()) return false; // Attempt to get rule information from the activity ISeqRuleset hiddenRules = walk.getPreSeqRules(); String result = null; if (hiddenRules != null) { result = hiddenRules.evaluate(SeqRuleset.RULE_TYPE_HIDDEN, walk, false); } // If the rule evaluation did not return null, the activity // must be hidden. if (result != null) { node.setHidden(true); return false; } // Check if this activity is prevented from activation if (walk.getPreventActivation() && !walk.getIsActive()) { // Looks like we need a candidate if (cur == null) return false; // If we're not looking at the candidate or its parent, then we don't want to include if (walk != cur && walk != cur.getParent()) return false; } return true; } /** * Checks if the identified activity is allowed to be considered for * delivery during a sequencing process.<br> * * @param iTarget * Target activity for limit condition evaluations. * * @return <code>true</code> if the activity should not be considered for * delivery, otherwise <code>false</code>. */ private boolean checkActivity(SeqActivity iTarget) { // This is an implementation of UP.5. if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "checkActivity"); System.out.println(" ::--> Target: " + iTarget.getID()); } boolean disabled = false; String result = null; // Attempt to get rule information from the activity node ISeqRuleset disabledRules = iTarget.getPreSeqRules(); if (disabledRules != null) { result = disabledRules.evaluate(SeqRuleset.RULE_TYPE_DISABLED, iTarget, mRetry); } // If the rule evaluation did not return null, the activity must // be disabled. if (result != null) { disabled = true; } if (!disabled) { // Evaluate other limit conditions associated with the activity. disabled = evaluateLimitConditions(iTarget); } if (_Debug) { System.out.println(" ::--> " + disabled); System.out.println(" :: ADLSequencer --> END - " + "checkActivity"); } return disabled; } /** * This method is used to inform the sequencer to clear one of the * activity's objective's measures -- set it to 'unknown'. * * @param iID * ID of the activity whose measure has changed. * * @param iObjID * ID of the objective whose measure has changed. * */ public void clearAttemptObjMeasure(String iID, String iObjID) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "clearAttemptObjMeasure"); System.out.println(" ::--> Target activity: " + iID); System.out.println(" ::--> Objective: " + iObjID); } // Find the target activty SeqActivity target = getActivity(iID); // Make sure the activity exists if (target != null) { // Make sure the activity is a valid target for status changes // -- the active leaf current activity if (target.getIsActive()) { // If the activity is a leaf and is the current activity if (!target.hasChildren(false) && mSeqTree.getCurrentActivity() == target) { boolean statusChange = target.clearObjMeasure(iObjID); if (statusChange) { target.getObjIDs(iObjID, false); // Revalidate the navigation requests validateRequests(); } } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Target not active"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Activity does not exist"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "clearAttemptObjMeasure"); } } /** * Clear the current activity; this is done unconditionally. */ public void clearSeqState() { if (_Debug) { System.out.println(" ::--> Clear Session"); } SeqActivity temp = null; mSeqTree.setCurrentActivity(temp); mSeqTree.setFirstCandidate(temp); } /** * This is a utility method that clears the Suspended Activity upon launch * of content. * * @param iTarget * Identififies the target activity for delivery. */ private void clearSuspendedActivity(SeqActivity iTarget) { // This method implements the Clear Supsended Activity Subprocess (DB.2) if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "clearSuspendedActivity"); } SeqActivity act = mSeqTree.getSuspendAll(); if (iTarget == null) { if (_Debug) { System.out.println(" ::--> Nothing to deliver"); } act = null; } if (act != null) { if (iTarget != act) { ISeqActivity common = findCommonAncestor(iTarget, act); while (act != common) { act.setIsSuspended(false); List<SeqActivity> children = act.getChildren(false); if (children != null) { boolean done = false; for (int i = 0; i < children.size() && !done; i++) { SeqActivity lookAt = children.get(i); if (lookAt.getIsSuspended()) { act.setIsSuspended(true); done = true; } } } act = act.getParent(); } } else { if (_Debug) { System.out.println(" ::--> Target is the Suspended Act"); } } // Clear the suspended activity SeqActivity temp = null; mSeqTree.setSuspendAll(temp); } else { if (_Debug) { System.out.println(" ::--> Nothing to clear"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "clearSuspendedActivity"); } } /** * The method is the exit point of the overall sequencing loop. When it is * invoked, it is assumed that the activity identified for delivery has been * validated by the Delivery Request Process. This method performs necessery * activity tree management and returns sufficient information to the RTE * launching the resource(s) associated with the identified activity. * * @param iTarget * The activity identified for delivery. * * @param oLaunch * An 'out' parameter that provides information to the RTE for * launching the resources associated with the activity. */ private void contentDelivery(String iTarget, ADLLaunch oLaunch) { // This method implements the Content Delivery Environment Process // (DB.2) if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "contentDelivery"); System.out.println(" ::--> " + iTarget); } SeqActivity target = getActivity(iTarget); boolean done = false; if (target == null) { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; oLaunch.mEndSession = mEndSession; done = true; } SeqActivity cur = mSeqTree.getFirstCandidate(); if (cur != null && !done) { if (cur.getIsActive()) { if (_Debug) { System.out.println(" ::--> ERROR : " + "Current activity still active."); } oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; oLaunch.mEndSession = mEndSession; done = true; } } if (!done) { // Clear any 'suspended' activity clearSuspendedActivity(target); // End any active attempts terminateDescendentAttempts(target); // Begin all required new attempts List<SeqActivity> begin = new ArrayList<SeqActivity>(); SeqActivity walk = target; while (walk != null) { begin.add(walk); walk = walk.getParent(); } if (begin.size() > 0) { for (int i = begin.size() - 1; i >= 0; i--) { walk = begin.get(i); if (_Debug) { System.out.println(" ::--> BEGIN >> " + walk.getID()); } if (!walk.getIsActive()) { if (walk.getIsTracked()) { if (walk.getIsSuspended()) { walk.setIsSuspended(false); } else { // Initialize tracking information for the new // attempt walk.incrementAttempt(); } } walk.setIsActive(true); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Empty begin List"); } } // Set the tree in the appropriate state mSeqTree.setCurrentActivity(target); mSeqTree.setFirstCandidate(target); // Fill in required launch information oLaunch.mEndSession = mEndSession; oLaunch.mActivityID = iTarget; oLaunch.mResourceID = target.getResourceID(); oLaunch.mStateID = target.getStateID(); if (oLaunch.mStateID == null) { oLaunch.mStateID = iTarget; } oLaunch.mNumAttempt = target.getNumAttempt() + target.getNumSCOAttempt(); oLaunch.mMaxTime = target.getAttemptAbDur(); // Create auxilary services List Hashtable<String, ADLAuxiliaryResource> services = new Hashtable<String, ADLAuxiliaryResource>(); ADLAuxiliaryResource test = null; walk = target; // Starting at the target activity, walk up the tree adding services while (walk != null) { List<ADLAuxiliaryResource> curSet = walk.getAuxResources(); if (curSet != null) { for (int i = 0; i < curSet.size(); i++) { ADLAuxiliaryResource res = null; res = curSet.get(i); // If the resource isn't already included in the set, // add it test = services.get(res.mType); if (test == null) { services.put(res.mType, res); } } } // Walk up the tree walk = walk.getParent(); } if (services.size() > 0) { oLaunch.mServices = services; } } if (_Debug) { System.out.println(" ::--> Content Delivery Valididation"); } validateRequests(); oLaunch.mNavState = mSeqTree.getValidRequests(); // Make sure Continue Exit is not enabled for non-content if (oLaunch.mSeqNonContent != null) { oLaunch.mNavState.mContinueExit = false; } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "contentDelivery"); } } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Implementation of SeqReportActivityStatus -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * This method validates a delivery request by waling the activity tree from * the root to the identifed activity. At each step, disabled sequencing * rules and limit conditions are evaluated. If the identified activity can * be delivered, an <code>ADLLaunch</code> object is initialized with the * required information for the RTE. <br> * <b>Internal Sequencing Process</b><br> * <br> * * @param iTarget * The activity identified for delivery. * * @param iTentative * Should the launch (<code>ADLLaunch</code>) information be * provided? * * @param oLaunch * The launch (<code>ADLLaunch</code>) information to be * provided to the RTE. * * @return <code>true</code> if the identifed activity is valid for delive * otherwise <code>false</code>. */ private boolean doDeliveryRequest(String iTarget, boolean iTentative, ADLLaunch oLaunch) { // This method implements DB.1. Also, if the delivery request is not // tentative, it invokes the Content Delivery Environment Process. if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "doDeliveryRequest"); System.out.println(" ::--> " + iTarget); System.out.println(" ::--> REAL? " + ((iTentative) ? "NO" : "YES")); } boolean deliveryOK = true; // Make sure the identified activity exists in the tree. SeqActivity act = getActivity(iTarget); if (act == null) { // If there is no activity identified for delivery, there is nothing // to delivery -- indentify non-Sequenced content deliveryOK = false; if (_Debug) { System.out.println(" ::--> No Delivery Request"); } if (!iTentative) { if (mExitCourse) { oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_COURSECOMPLETE; } else { if (mEndSession) { oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_EXITSESSION; } else { oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } } } } // Confirm the target activity is a leaf if (deliveryOK && act.hasChildren(false)) { deliveryOK = false; oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; oLaunch.mEndSession = mEndSession; if (_Debug) { System.out.println(" ::--> Activity is not a leaf"); } } else if (deliveryOK) { boolean ok = true; // Walk the path from the target activity to the root, checking each // activity. while (act != null && ok) { if (_Debug) { System.out.println(" ::--> Validating --> " + act.getID()); } ok = !checkActivity(act); if (ok) { act = act.getParent(); } } if (!ok) { if (_Debug) { System.out.println(" ::--> Some activity did not validate"); } deliveryOK = false; oLaunch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } } // If the delivery request not a tentative request, prepare for deliver if (!iTentative) { // Did the request validate if (deliveryOK) { contentDelivery(iTarget, oLaunch); validateRequests(); } else { oLaunch.mEndSession = mEndSession || mExitCourse; if (!oLaunch.mEndSession) { validateRequests(); oLaunch.mNavState = mSeqTree.getValidRequests(); } } } if (_Debug) { System.out.println(" ::--> " + deliveryOK); System.out.println(" :: ADLSequencer --> END - " + "doDeliveryRequest"); } return deliveryOK; } /** * Validates the indicated navigation request according to the IMS * Navigation Request Process Behavior. * * @param iRequest * The Request being valididated. * * @return <code>true</code> if the indentified request is valid according * to the IMS Navigation Request Process. */ private boolean doIMSNavValidation(int iRequest) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - doIMSNavValidation"); } boolean ok = true; // Assume the navigation request is valid boolean process = true; // If this is a new session, we start at the root. boolean newSession = false; SeqActivity cur = mSeqTree.getCurrentActivity(); SeqActivity parent = null; if (cur == null) { newSession = true; } else { parent = cur.getParent(); } // Validate the pending navigation request before processing it. // The following tests implement the validation logic of NB.2.1; it // covers all cases where the requst, itself, is invalid. switch (iRequest) { case SeqNavRequests.NAV_START: if (!newSession) { if (_Debug) { System.out.println(" ::--> 'Start' not valid"); } process = false; } break; case SeqNavRequests.NAV_RESUMEALL: ok = true; if (!newSession) { ok = false; } else if (mSeqTree.getSuspendAll() == null) { ok = false; } // Request not valid if (!ok) { if (_Debug) { System.out.println(" ::--> 'ResumeAll' not valid"); } process = false; } break; case SeqNavRequests.NAV_CONTINUE: // Request not valid if (newSession) { if (_Debug) { System.out.println(" ::--> 'Continue' not valid"); } process = false; } else { if (parent == null || !parent.getControlModeFlow()) { if (_Debug) { System.out.println(" ::--> 'Continue' not valid"); } process = false; } } break; case SeqNavRequests.NAV_PREVIOUS: if (newSession) { if (_Debug) { System.out.println(" ::--> 'Previous' not valid"); } process = false; } else { if (parent != null) { if (!parent.getControlModeFlow() || parent.getControlForwardOnly()) { if (_Debug) { System.out.println(" ::--> 'Previous' not valid " + "-- Control Mode"); } process = false; } } else { if (_Debug) { System.out.println(" ::--> 'Previous' not valid " + "-- NULL Parent"); } process = false; } } break; case SeqNavRequests.NAV_ABANDON: ok = true; if (newSession) { ok = false; } else if (!cur.getIsActive()) { ok = false; } // Request is not valid if (!ok) { if (_Debug) { System.out.println(" ::--> 'Abandon' not valid"); } process = false; } break; case SeqNavRequests.NAV_ABANDONALL: if (newSession) { if (_Debug) { System.out.println(" ::--> 'AbandonAll' not valid"); } process = false; } break; case SeqNavRequests.NAV_SUSPENDALL: if (newSession) { if (_Debug) { System.out.println(" ::--> 'Abandon' not valid"); } process = false; } break; case SeqNavRequests.NAV_EXIT: if (newSession) { ok = false; } else if (!cur.getIsActive()) { ok = false; } // Request not valid if (!ok) { if (_Debug) { System.out.println(" ::--> 'Exit' not valid"); } process = false; } break; case SeqNavRequests.NAV_EXITALL: if (newSession) { if (_Debug) { System.out.println(" ::--> 'ExitAll' not valid"); } process = false; } break; default: if (_Debug) { System.out.println(" ::--> Invalid navigation request: " + iRequest); } process = false; } if (_Debug) { System.out.println(" ::--> " + process); System.out.println(" :: ADLSequencer --> END - doIMSNavValidation"); } return process; } /** * Applies the Overall Rollup Process to the target activity. <br> * <b>Internal Sequencing Process</b><br> * <br> * * @param ioTarget * Identifies the activity where rollup is applied. * * @param ioRollupSet * Identifies the set of activities remaining. */ private void doOverallRollup(SeqActivity ioTarget, Hashtable<String, Integer> ioRollupSet) { // This method implements the loop of RB.1.5. The other rollup process // are encapsulated in the RollupRuleset object. if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - doOverallRollup"); System.out.println(" ::--> Target: " + ioTarget.getID()); } // Attempt to get Rollup Rule information from the activity node ISeqRollupRuleset rollupRules = ioTarget.getRollupRules(); if (rollupRules == null) { rollupRules = new SeqRollupRuleset(); } // Apply the rollup processes to the activity rollupRules.evaluate(ioTarget); if (_Debug) { boolean objMeasureStatus = ioTarget.getObjMeasureStatus(false); double objMeasure = ioTarget.getObjMeasure(false); boolean objStatus = ioTarget.getObjStatus(false); boolean objSatisfied = ioTarget.getObjSatisfied(false); boolean proStatus = ioTarget.getProgressStatus(false); boolean proCompleted = ioTarget.getAttemptCompleted(false); System.out.println(" ::--> RESULTS"); System.out.println(" :: OBJ Measure :: " + objMeasureStatus + " // " + objMeasure); System.out.println(" :: OBJ Status :: " + objStatus + " // " + objSatisfied); System.out.println(" :: Progress :: " + proStatus + " // " + proCompleted); } // Remove this activity from the rollup set ioRollupSet.remove(ioTarget.getID()); if (_Debug) { System.out.println(" :: ADLSequencer --> END - doOverallRollup"); } } /** * Reorder the children of a cluster to be considered for sequencing. * * @param ioCluster * Cluster to be prepared. */ private void doRandomize(SeqActivity ioCluster) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - doRandomize"); System.out.println(" ::--> Target: " + ioCluster.getID()); } // Make sure this is a cluster if (ioCluster.getChildren(true) != null) { Random gen = new Random(); List<SeqActivity> all = ioCluster.getChildren(false); if (_Debug) { System.out.println(" ::--> Cluster has '" + all.size() + "' " + " children to randomize"); } List<Integer> set = null; boolean ok = false; int rand = 0; int num = 0; int lookUp = 0; // Reorder the 'selected' child set if neccessary if (ioCluster.getReorderChildren()) { List<SeqActivity> reorder = new ArrayList<SeqActivity>(); set = new ArrayList<Integer>(); for (int i = 0; i < all.size(); i++) { // Pick an unselected child ok = false; while (!ok) { rand = gen.nextInt(); num = Math.abs(rand % all.size()); lookUp = set.indexOf(Integer.valueOf(num)); if (lookUp == -1) { set.add(Integer.valueOf(num)); reorder.add(all.get(num)); if (_Debug) { System.out.println(" ::--> PLACED --> " + num); } ok = true; } } } // Assign the current set of active children to this cluster ioCluster.setChildren(reorder, false); } else { if (_Debug) { System.out.println(" ::--> Don't Reorder"); } } } else { if (_Debug) { System.out.println(" ::--> Not A Cluster"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - doRandomize"); } } /** * Prepare the children of a cluster to be considered for sequencing. * * @param ioCluster * Cluster to be prepared. */ private void doSelection(SeqActivity ioCluster) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - doSelection"); System.out.println(" ::--> Target: " + ioCluster.getID()); } // Make sure this is a cluster if (ioCluster.getChildren(true) != null) { Random gen = new Random(); int count = ioCluster.getSelectCount(); List<SeqActivity> all = ioCluster.getChildren(true); if (_Debug) { System.out.println(" ::--> Cluster has '" + all.size() + "' " + " children"); } List<SeqActivity> children = null; List<Integer> set = null; boolean ok = false; int rand = 0; int num = 0; int lookUp = 0; // First select the Select Count number of children if (count > 0) { // Check to see if the count exceeds the number of children if (count < all.size()) { if (_Debug) { System.out.println(" ::--> Selecting --> " + count); } // Select count activities from the set of children children = new ArrayList<SeqActivity>(); set = new ArrayList<Integer>(); while (set.size() < count) { // Find an unselected child of the cluster ok = false; while (!ok) { rand = gen.nextInt(); num = Math.abs(rand % all.size()); lookUp = set.indexOf(Integer.valueOf(num)); if (lookUp == -1) { set.add(Integer.valueOf(num)); ok = true; if (_Debug) { System.out.println(" ::--> ADDED --> " + num); } } } } // Create the selected child List for (int i = 0; i < all.size(); i++) { lookUp = set.indexOf(Integer.valueOf(i)); if (lookUp != -1) { children.add(all.get(i)); } } // Assign the selected set of children to the cluster ioCluster.setChildren(children, false); } else { if (_Debug) { System.out.println(" ::--> All Children Selected"); } } } else { if (_Debug) { System.out.println(" ::--> No Children Selected"); } } } else { if (_Debug) { System.out.println(" ::--> Not A Cluster"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - doSelection"); } } /** * This method processes the indicated sequencing request on the activity * tree, attempting to identify an activity for delivery. <br> * <b>Internal Sequencing Process</b><br> * <br> * * @param iRequest * The sequencing request to be processed. * * @return An activity identified for delivery (<code>String</code>) or * <code>null</code> if the sequencing request did not identify an * activity for delivery. */ private String doSequencingRequest(String iRequest) { // This method implements the Sequencing Request Process (SB.2.12) if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "doSequencingRequest"); System.out.println(" ::--> " + iRequest); } String delReq = null; // Clear global state mEndSession = false; // All sequencing requests are processed from the First Candidate SeqActivity from = mSeqTree.getFirstCandidate(); if (iRequest.equals(ADLSequencer.SEQ_START)) { // This block implements the Start Sequencing Request Process (SB.2. if (_Debug) { System.out.println(" ::--> Process 'Start' request."); } // Make sure this request will begin a new session if (from == null) { // Begin traversing the activity tree from the root Walk walk = new Walk(); walk.at = mSeqTree.getRoot(); boolean success = processFlow(ADLSequencer.FLOW_FORWARD, true, walk, false); if (success) { // Delivery request is where flow stopped. delReq = walk.at.getID(); } } else { if (_Debug) { System.out.println(" ::--> Session already begun"); } } } else if (iRequest.equals(ADLSequencer.SEQ_RESUMEALL)) { // This block implements the Resume All Sequencing Request Process // (SB.2.6) if (_Debug) { System.out.println(" ::--> Process 'Resume All' request."); } // Make sure this request will begin a new session if (from == null) { SeqActivity resume = mSeqTree.getSuspendAll(); if (resume != null) { delReq = resume.getID(); } else { if (_Debug) { System.out.println(" ::--> No suspended activity"); } } } else { if (_Debug) { System.out.println(" ::--> Session already begun"); } } } else if (iRequest.equals(ADLSequencer.SEQ_CONTINUE)) { // This block implements the Continue Sequencing Request Process // (SB.2.7) if (_Debug) { System.out.println(" ::--> Process 'Continue' request."); } // Make sure the session has already started if (from != null) { // Confirm 'flow' is enabled SeqActivity parent = from.getParent(); if (parent == null || parent.getControlModeFlow()) { // Begin traversing the activity tree from the root Walk walk = new Walk(); walk.at = from; boolean success = processFlow(ADLSequencer.FLOW_FORWARD, false, walk, false); if (success) { // Delivery request is where flow stopped. delReq = walk.at.getID(); } else { if (_Debug) { System.out.println(" :+: CONTINUE FAILED :+: --> " + walk.at.getID()); } } } } else { if (_Debug) { System.out.println(" ::--> Session hasn't begun"); } } } else if (iRequest.equals(ADLSequencer.SEQ_EXIT)) { // This block implements the Exit Sequencing Request Process // (SB.2.11) if (_Debug) { System.out.println(" ::--> Process 'Exit' request."); } // Make sure the session has already started if (from != null) { if (!from.getIsActive()) { ISeqActivity parent = from.getParent(); if (parent == null) { // The sequencing session is over -- set global state mEndSession = true; } } else { if (_Debug) { System.out.println(" ::--> Activity is still active"); } } } else { if (_Debug) { System.out.println(" ::--> Session hasn't begun"); } } } else if (iRequest.equals(ADLSequencer.SEQ_PREVIOUS)) { // This block implements the Previous Sequencing Request Process // (SB.2.8) if (_Debug) { System.out.println(" ::--> Process 'Previous' request."); } // Make sure the session has already started if (from != null) { // Confirm 'flow' is enabled SeqActivity parent = from.getParent(); if (parent == null || parent.getControlModeFlow()) { // Begin traversing the activity tree from the root Walk walk = new Walk(); walk.at = from; boolean success = processFlow(ADLSequencer.FLOW_BACKWARD, false, walk, false); if (success) { // Delivery request is where flow stopped. delReq = walk.at.getID(); } } } else { if (_Debug) { System.out.println(" ::--> Session hasn't begun"); } } } else if (iRequest.equals(ADLSequencer.SEQ_RETRY)) { // This block implements the Retry Sequencing Request Process // (SB.2.10) if (_Debug) { System.out.println(" ::--> Process 'Retry' request."); } // Make sure the session has already started if (from != null) { if (mExitAll || (!(from.getIsActive() || from.getIsSuspended()))) { if (from.getChildren(false) != null) { Walk walk = new Walk(); walk.at = from; // Set 'Retry' flag setRetry(true); boolean success = processFlow(ADLSequencer.FLOW_FORWARD, true, walk, false); // Reset 'Retry' flag setRetry(false); if (success) { delReq = walk.at.getID(); } } else { delReq = from.getID(); } } else { if (_Debug) { System.out.println(" ::--> Activity is active or suspended"); } } } else { if (_Debug) { System.out.println(" ::--> Session hasn't begun"); } } } else { // This block implements the Choice Sequencing Request Process // (SB.2) if (_Debug) { System.out.println(" ::--> Process 'Choice' request."); } // The sequencing request identifies the target activity SeqActivity target = getActivity(iRequest); if (target != null) { boolean process = true; SeqActivity parent = target.getParent(); // Check if the activity should be considered. if (!target.getIsSelected()) { // Exception SB.2.9-2 process = false; if (_Debug) { System.out.println(" ::--> Activity not in parent's " + "set of avaliable children"); } } if (process) { SeqActivity walk = target.getParent(); // Walk up the tree evaluating 'Hide from Choice' rules. while (walk != null) { // Attempt to get rule information from the activity ISeqRuleset hideRules = walk.getPreSeqRules(); String result = null; if (hideRules != null) { result = hideRules.evaluate(SeqRuleset.RULE_TYPE_HIDDEN, walk, false); } // If the rule evaluation did not return null, the // activity // must be hidden. if (result != null) { // Exception SB.2.9-3 walk = null; process = false; if (_Debug) { System.out.println(" ::--> Activity hidden"); } } else { walk = walk.getParent(); } } } // Confirm the control mode is valid if (process) { if (parent != null) { if (!parent.getControlModeChoice()) { // Exception SB.2.9-4 process = false; if (_Debug) { System.out.println(" ::--> Invalid control mode"); } } } } SeqActivity common = mSeqTree.getRoot(); if (process) { if (from != null) { common = findCommonAncestor(from, target); if (common == null) { process = false; if (_Debug) { System.out.println(" ::--> ERROR : Invalid ancestor"); } } } else { // If the sequencing session has not begun, start at the // root from = common; } if (_Debug) { System.out.println(" :: CHOICE PROCESS ::"); if (from != null) { System.out.println(" :: F : " + from.getID()); } else { System.out.println(" :: F : NULL"); } if (target != null) { System.out.println(" :: T : " + target.getID()); } if (common != null) { System.out.println(" :: C : " + common.getID()); } else { System.out.println(" :: C : NULL"); } } // Choice Case #1 -- The current activity was selected if (from == target) { if (_Debug) { System.out.println(" ::--> Choice Case #1"); } // Nothing more to do... } else if (from != null && from.getParent() == target.getParent()) { // Choice Case #2 -- The current activity and target are in // the // same cluster if (_Debug) { System.out.println(" ::--> Choice Case #2"); } int dir = ADLSequencer.FLOW_FORWARD; if (target.getActiveOrder() < from.getActiveOrder()) { dir = ADLSequencer.FLOW_BACKWARD; } SeqActivity walk = from; // Make sure no control modes or rules prevent the // traversal while (walk != target && process) { process = evaluateChoiceTraversal(dir, walk); if (dir == ADLSequencer.FLOW_FORWARD) { walk = walk.getNextSibling(false); } else { walk = walk.getPrevSibling(false); } } } // Choice Case #3 -- Path to the target is forward in the // tree else if (from == common) { if (_Debug) { System.out.println(" ::--> Choice Case #3"); } SeqActivity walk = target.getParent(); while (walk != from && process) { process = evaluateChoiceTraversal(ADLSequencer.FLOW_FORWARD, walk); // Test prevent Activation if (process) { if (!walk.getIsActive() && walk.getPreventActivation()) { // Exception 2.9-6 process = false; continue; } } walk = walk.getParent(); } // Evaluate at the common ancestor if (process) { process = evaluateChoiceTraversal(ADLSequencer.FLOW_FORWARD, walk); } } // Choice Case #4 -- Path to target is backward in the tree else if (target == common) { if (_Debug) { System.out.println(" ::--> Choice Case #4"); } // Don't need to test choiceExit on the current activity // because the navigation request validated. SeqActivity walk = from.getParent(); while (walk != target && process) { // Need to make sure that none of the 'exiting' // activities // prevents us from reaching the common ancestor. process = walk.getControlModeChoiceExit(); walk = walk.getParent(); } } // Choice Case #5 -- Target is a descendent of the ancestor else { if (_Debug) { System.out.println(" ::--> Choice Case #5"); } SeqActivity con = null; SeqActivity walk = from.getParent(); // Walk up the tree to the common ancestor while (walk != common && process) { process = walk.getControlModeChoiceExit(); if (process && con == null) { if (walk.getConstrainChoice()) { con = walk; } } walk = walk.getParent(); } // Evaluate constrained choice set if (process && con != null) { Walk walkCon = new Walk(); walkCon.at = con; if (target.getCount() > con.getCount()) { processFlow(FLOW_FORWARD, false, walkCon, true); } else { processFlow(FLOW_BACKWARD, false, walkCon, true); } if (_Debug) { System.out.println(" ::--> Constrained Choice Eval"); System.out.println(" ::--> Stopped at --> " + walkCon.at.getID()); } if (target.getParent() != walkCon.at && target != walkCon.at) { // Exception SB.2.9-8 process = false; } } // Walk down the tree to the target walk = target.getParent(); while (walk != common && process) { process = evaluateChoiceTraversal(ADLSequencer.FLOW_FORWARD, walk); // Test prevent Activation if (process) { if (!walk.getIsActive() && walk.getPreventActivation()) { // Exception 2.9-6 process = false; continue; } } walk = walk.getParent(); } // Evaluate the common ancestor if (process) { process = evaluateChoiceTraversal(ADLSequencer.FLOW_FORWARD, walk); } } // Did we reach the target successfully? if (process) { // Is the target a cluster if (target.getChildren(false) != null) { Walk walk = new Walk(); walk.at = target; boolean success = processFlow(ADLSequencer.FLOW_FORWARD, true, walk, false); if (success) { delReq = walk.at.getID(); } else { if (_Debug) { System.out.println(" ::--> Failed to find leaf"); System.out.println(" ::--> Moving Current Activity"); if (common == null) { System.out.println(" ::--> NULL"); } else { System.out.println(" ::--> " + common.getID()); } } if (mSeqTree.getCurrentActivity() != null && common != null) { terminateDescendentAttempts(common); endAttempt(common, false); // Move the current activity mSeqTree.setCurrentActivity(target); mSeqTree.setFirstCandidate(target); } } } else { delReq = target.getID(); } } } } else { // Exception SB.2.9-1 if (_Debug) { System.out.println(" ::--> Target does not exist in the tree"); } } } if (_Debug) { System.out.println(" ::--> " + delReq); System.out.println(" ::--> " + (mEndSession || mExitCourse)); System.out.println(" :: ADLSequencer --> END - " + "doSequencingRequest"); } return delReq; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Implementation of SeqNavigation -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * Processes a specified termination request on the current activity tree. * <br> * <b>Internal Sequencing Process</b><br> * <br> * * @param iRequest * Identifies the termination request being processed. * * @param iTentative * Indicates if attempts should ended. * * @return May return a sequencing request (<code>String</code>) that * over rides any existing sequencing request, or <code>null</code>. */ private String doTerminationRequest(String iRequest, boolean iTentative) { // This method implements the Termination Request Process (TB.2.3). // // The Termination Request Process ensures the activity tree is in the // most current state, to ensure that subsequent sequencing requests ar // processed on up todate data. // // The termination request is processed from the current activity and // determines what the first candidate for sequencing should is. // If this is a 'real' termination request, the current activity is mov // to the identified first candidate activity. if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "doTerminationRequest"); System.out.println(" ::--> Request: " + iRequest); System.out.println(" ::--> Real? " + ((iTentative) ? "NO" : "YES")); } // The Termination Request Process may return a sequencing request String seqReq = null; mExitAll = false; // Ensure the request exists if (iRequest == null) { mValidTermination = false; if (_Debug) { System.out.println(" ::--> NULL request"); System.out.println(" :: ADLSequencer --> END - " + "doTerminationRequest"); } return seqReq; } // The Sequencing Request Process will always begin processing at the // 'first candidate'. // Assume the first candidate for sequencing is the current activity. SeqActivity cur = mSeqTree.getCurrentActivity(); if (cur != null) { mSeqTree.setFirstCandidate(cur); } else { mValidTermination = false; if (_Debug) { System.out.println(" ::--> No current activity"); System.out.println(" :: ADLSequencer --> END - " + "doTerminationRequest"); } return seqReq; } // Apply the termination request if (iRequest.equals(ADLSequencer.TER_EXIT)) { // Make sure the current activity is active. if (cur.getIsActive()) { // End the attempt on the current activity endAttempt(cur, iTentative); // Evaluate exit action rules evaluateExitRules(iTentative); // Evaluate post conditions boolean exited = false; do { exited = false; // Only process post conditions on the first candidate SeqActivity process = mSeqTree.getFirstCandidate(); // Make sure we are not at the root if (!mExitCourse) { // This block implements the Sequencing Post Condition // Rule // Subprocess (SB.2.2) if (_Debug) { System.out.println(" ::--> Evaluating 'POST' at -- " + process.getID()); } // Attempt to get rule information from the activity ISeqRuleset postRules = process.getPostSeqRules(); if (postRules != null) { String result = null; result = postRules.evaluate(SeqRuleset.RULE_TYPE_POST, process, false); if (result != null) { if (_Debug) { System.out.println(" ::--> " + result); } // This set of ifs implement TB.2.2 if (result.equals(SeqRule.SEQ_ACTION_RETRY)) { // Override any existing sequencing request seqReq = ADLSequencer.SEQ_RETRY; // If we are processing the root activity, // behave // as if this where an exitAll if (process == mSeqTree.getRoot()) { // Break from the current loop and jump // to the // next case iRequest = ADLSequencer.TER_EXITALL; } } else if (result.equals(SeqRule.SEQ_ACTION_CONTINUE)) { // Override any existing sequencing request seqReq = ADLSequencer.SEQ_CONTINUE; } else if (result.equals(SeqRule.SEQ_ACTION_PREVIOUS)) { // Override any existing sequencing request seqReq = ADLSequencer.SEQ_PREVIOUS; } else if (result.equals(SeqRule.SEQ_ACTION_EXITALL)) { // Break from the current loop and jump to // the // next case iRequest = ADLSequencer.TER_EXITALL; } else if (result.equals(SeqRule.SEQ_ACTION_EXITPARENT)) { process = process.getParent(); if (process == null) { if (_Debug) { System.out.println(" ::--> ERROR :: " + " No parent to exit"); } } else { mSeqTree.setFirstCandidate(process); endAttempt(process, iTentative); exited = true; } } else if (result.equals(SeqRule.SEQ_ACTION_RETRYALL)) { // Override any existing sequencing request seqReq = ADLSequencer.SEQ_RETRY; // Break from the current loop and jump to // the // next case iRequest = ADLSequencer.TER_EXITALL; } else if (process == mSeqTree.getRoot()) { // Exited Root with no postcondition rules // End the Course mExitCourse = true; } } else { if (_Debug) { System.out.println(" ::--> NULL Evaluation"); } } } else if (process == mSeqTree.getRoot()) { // Exited Root with no postcondition rules // End the Course mExitCourse = true; } } else { if (_Debug) { System.out.println(" --> Exited Course"); seqReq = ADLSequencer.SEQ_EXIT; } } } while (exited); } else { if (_Debug) { System.out.println(" ::--> INVALID :: " + "activity inactive"); } mValidTermination = false; } } // Double check for an EXIT request if (iRequest.equals(ADLSequencer.TER_EXIT)) { // Already handled } else if (iRequest.equals(ADLSequencer.TER_EXITALL)) { if (_Debug) { System.out.println(" ::--> Processing EXIT ALL"); } // Don't modify the activity tree if this is only a tentative exit if (!iTentative) { SeqActivity process = mSeqTree.getFirstCandidate(); if (process.getIsActive()) { endAttempt(process, false); } terminateDescendentAttempts(mSeqTree.getRoot()); endAttempt(mSeqTree.getRoot(), false); // only exit if we're not retrying the root if (!StringUtils.equals(seqReq, ADLSequencer.SEQ_RETRY)) { seqReq = ADLSequencer.SEQ_EXIT; } // Start any subsequent seqencing request from the root mSeqTree.setFirstCandidate(mSeqTree.getRoot()); } else { // Although this was a tentative evaluation, remember that we // processed the exitAll so that a retry from the root can be // tested // mExitAll = true; } // Start any subsequent seqencing request from the root mSeqTree.setFirstCandidate(mSeqTree.getRoot()); } else if (iRequest.equals(ADLSequencer.TER_SUSPENDALL)) { // Don't modify the activty tree if this is only a tentative exit if (!iTentative) { SeqActivity process = mSeqTree.getFirstCandidate(); if (process.getIsActive()) { // Invoke rollup invokeRollup(process, null); mSeqTree.setSuspendAll(process); // Check to see if the SCO's learner attempt ended if (!process.getIsSuspended()) { process.incrementSCOAttempt(); } } else { if (!process.getIsSuspended()) { mSeqTree.setSuspendAll(process.getParent()); // Make sure there was a an activity to suspend if (mSeqTree.getSuspendAll() == null) { mValidTermination = false; } } } if (mValidTermination) { SeqActivity start = mSeqTree.getSuspendAll(); // This process suspends all clusters up to the root while (start != null) { start.setIsActive(false); start.setIsSuspended(true); start = start.getParent(); } } } // Start any subsequent seqencing request from the root mSeqTree.setFirstCandidate(mSeqTree.getRoot()); } else if (iRequest.equals(ADLSequencer.TER_ABANDON)) { // Don't modify the activty tree if this is only a tentativen exit if (!iTentative) { SeqActivity process = mSeqTree.getFirstCandidate(); if (_Debug) { System.out.println(" --> CLEARING STATE ABANDON"); } // Ignore any status values reported by the content process.setProgress(ADLTracking.TRACK_UNKNOWN); process.setObjSatisfied(null, ADLTracking.TRACK_UNKNOWN); process.clearObjMeasure(null); process.setIsActive(false); } } else if (iRequest.equals(ADLSequencer.TER_ABANDONALL)) { // Don't modify the activty tree if this is only a tentative exit if (!iTentative) { SeqActivity process = mSeqTree.getFirstCandidate(); if (_Debug) { System.out.println(" --> CLEARING STATE ABANDONALL"); } // Ignore any status values reported by the content process.setProgress(ADLTracking.TRACK_UNKNOWN); process.setObjSatisfied(null, ADLTracking.TRACK_UNKNOWN); process.clearObjMeasure(null); while (process != null) { process.setIsActive(false); process = process.getParent(); } seqReq = ADLSequencer.SEQ_EXIT; // Start any subsequent seqencing request from the root mSeqTree.setFirstCandidate(mSeqTree.getRoot()); } } else { if (_Debug) { System.out.println(" ::--> INVALID :: " + "invalid request"); } mValidTermination = false; } // If this was a 'real' termination request, move the current activity if (!iTentative) { mSeqTree.setCurrentActivity(mSeqTree.getFirstCandidate()); } String tmpID = mSeqTree.getFirstCandidate().getID(); if (_Debug) { System.out.println(" ::--> SEQ REQ :: " + seqReq); System.out.println(" ::--> FIRST :: " + tmpID); System.out.println(" ::--> EXIT COURSE :: " + mExitCourse); System.out.println(" :: ADLSequencer --> END - " + "doTerminationRequest"); } return seqReq; } /** * End the attempt on the target activity and perform any required state * maintenance on the activity tree. * * @param iTarget * Activity for which an attempt will end. * * @param iTentative * Indicates if the attempt should 'really' end. */ private void endAttempt(SeqActivity iTarget, boolean iTentative) { // This is an implementation of the End Attempt Process (UP.4) if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - endAttempt"); if (iTarget != null) { System.out.println(" ::--> Target : " + iTarget.getID()); } else { System.out.println(" ::--> ERROR : NULL Activity"); } System.out.println(" ::--> REAL? -- " + ((iTentative) ? "NO" : "YES")); } if (iTarget != null) { List<SeqActivity> children = iTarget.getChildren(false); // Is the activity a tracked leaf if (children == null && iTarget.getIsTracked()) { // If the attempt was not suspended, perform attempt cleanup if (!iTarget.getIsSuspended()) { if (!iTarget.getSetCompletion()) { // If the content hasn't set this value, set it if (!iTarget.getProgressStatus(false)) { iTarget.setProgress(ADLTracking.TRACK_COMPLETED); } } if (!iTarget.getSetObjective()) { // If the content hasn't set this value, set it if (!iTarget.getObjStatus(false, true)) { iTarget.setObjSatisfied(ADLTracking.TRACK_SATISFIED); } } } } else if (children != null) { // The activity is a cluster, check if any of its children are // suspended. // Only set suspended state if this is a 'real' termiantion if (!iTentative) { iTarget.setIsSuspended(false); for (int i = 0; i < children.size(); i++) { SeqActivity act = children.get(i); if (act.getIsSuspended()) { iTarget.setIsSuspended(true); break; } } // If the cluster is not suspended check for selection and // randomization if (!iTarget.getIsSuspended()) { if (iTarget.getSelectionTiming().equals(SeqActivity.TIMING_EACHNEW)) { doSelection(iTarget); iTarget.setSelection(true); } if (iTarget.getRandomTiming().equals(SeqActivity.TIMING_EACHNEW)) { doRandomize(iTarget); iTarget.setRandomized(true); } } } } // The activity becomes inactive if this is a 'real' termination if (!iTentative) { iTarget.setIsActive(false); if (iTarget.getIsTracked()) { // Make sure satisfaction is updated according to measure iTarget.triggerObjMeasure(); } // Invoke rollup invokeRollup(iTarget, null); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - endAttempt"); } } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Private Methods -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * Evaluates if the identified activity can be skipped during a 'Choice' * sequencing request. * * @param iDirection * Indicates the direction the tree is to be traversed. * * @param iAt * Indicates the activity being considered. * * @return Indicates if the activity can be reached. */ private boolean evaluateChoiceTraversal(int iDirection, SeqActivity iAt) { // This method implements Choice Activity Traversal Subprocess SB.2.4 if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "evaluateChoiceTraversal"); System.out.println(" ::--> " + iDirection); if (iAt != null) { System.out.println(" ::--> " + iAt.getID()); } else { System.out.println(" ::--> ERROR : NULL starting point"); } } boolean success = true; // Make sure we have somewhere to start from if (iAt != null) { if (iDirection == ADLSequencer.FLOW_FORWARD) { // Attempt to get rule information from the activity node ISeqRuleset stopTrav = iAt.getPreSeqRules(); String result = null; if (stopTrav != null) { result = stopTrav.evaluate(SeqRuleset.RULE_TYPE_FORWARDBLOCK, iAt, false); } // If the rule evaluation does not return null, can't move // to the // activity's sibling if (result != null) { success = false; } } else if (iDirection == ADLSequencer.FLOW_BACKWARD) { SeqActivity parent = iAt.getParent(); if (parent != null) { success = !parent.getControlForwardOnly(); } } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid direction"); } success = false; } } else { success = false; } if (_Debug) { System.out.println(" ::--> " + success); System.out.println(" :: ADLSequencer --> END - " + "evaluateChoiceTraversal"); } return success; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Navigation Behavior -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * This method evaluates 'Exit' rules of all active clusters. <br> * <br> * This is an implementation of the Sequencing Exit Action Rules Subprocess * (TB.2.1). * * @param iTentative * Indicates if descendent activities should be terminated. */ private void evaluateExitRules(boolean iTentative) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - evaluateExitRules"); System.out.println(" ::--> REAL? -- " + ((iTentative) ? "NO" : "YES")); } // Clear global state mExitCourse = false; // Always begin processing at the current activity SeqActivity start = mSeqTree.getCurrentActivity(); SeqActivity exitAt = null; String exited = null; List<SeqActivity> path = new ArrayList<SeqActivity>(); if (start != null) { SeqActivity parent = start.getParent(); while (parent != null) { if (_Debug) { System.out.println(" ::--> Adding :: " + parent.getID()); } path.add(parent); parent = parent.getParent(); } // Starting at the root, walk down the tree to the current activity' // parent. while (path.size() > 0 && (exited == null)) { parent = path.get(path.size() - 1); path.remove(path.size() - 1); if (_Debug) { System.out.println(" ::--> Evaluating 'Exit' at -- " + parent.getID()); } // Attempt to get rule information from the activity node ISeqRuleset exitRules = parent.getExitSeqRules(); if (exitRules != null) { exited = exitRules.evaluate(SeqRuleset.RULE_TYPE_EXIT, parent, false); } // If the rule evaluation did not return null, the activity must // have exited. if (exited != null) { exitAt = parent; } } if (exited != null) { if (exitAt == mSeqTree.getRoot()) { if (_Debug) { System.out.println(" ::--> ROOT <<< Exited"); } } else { if (_Debug) { System.out.println(" ::--> " + exitAt.getID() + " <<< Exited"); } } // If this was a 'real' evaluation, end the appropriate // attempts. if (!iTentative) { // If an activity exited, end attempts at all remaining // cluster // on the 'active' branch. terminateDescendentAttempts(exitAt); // End the attempt on the 'exited' activity endAttempt(exitAt, false); // invokeRollup(cur, null); } // Sequencing requests begin at the 'exited' activity mSeqTree.setFirstCandidate(exitAt); } } else { if (_Debug) { System.out.println(" ::--> ERROR : NULL Current Activity"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - evaluateExitRules"); } } /** * Evaluate all limit conditions defined for the target activity. * * @param iTarget * Target activity for limit condition evaluations. * * @return <code>true</code> if the evaulation of limit condtions for the * target activity result in that activity becoming disabled, * otherwise <code>false</code>. */ private boolean evaluateLimitConditions(SeqActivity iTarget) { // This is an implementation of UP.1 if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "evaluateLimitConditions"); System.out.println(" ::--> Target: " + iTarget.getID()); } // For 2, we only test max attempt limits // Need to add all limit condition tests... boolean disabled = false; // Only test limitConditions if the activity is not active if (!iTarget.getIsActive() && !iTarget.getIsSuspended()) { if (iTarget.getAttemptLimitControl()) { disabled = iTarget.getNumAttempt() >= iTarget.getAttemptLimit(); } } if (_Debug) { System.out.println(" ::--> " + disabled); System.out.println(" :: ADLSequencer --> END - " + "evaluateLimitConditions"); } return disabled; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Termination Behavior -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * This method finds a common parent to two activities in the activity tree. * * @param iFrom * The starting activity. * * @param iTo * The destination activity. * * @return The common ancestor of both activities (<code>SeqActivity</code>) * or <code>null</code> if the process failed. */ private SeqActivity findCommonAncestor(SeqActivity iFrom, SeqActivity iTo) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - findCommonAncestor"); } SeqActivity ancestor = null; boolean done = false; SeqActivity stepFrom = null; // If either activity is 'null', no common parent if (iFrom == null || iTo == null) { done = true; } else { // Get the starting parents -- only look at clusters // This algorithm uses the exising 'selected' children. if (!iFrom.hasChildren(false)) { stepFrom = iFrom.getParent(); } else { stepFrom = iFrom; } if (!iTo.hasChildren(false)) { iTo = iTo.getParent(); } } while (!done) { // Test if the 'to' activity is a decendent of 'from' parent boolean success = isDescendent(stepFrom, iTo); // If we found the target activity, we are done if (success) { ancestor = stepFrom; done = true; continue; } // If this isn't the common parent, move up the tree if (!done) { stepFrom = stepFrom.getParent(); } } if (_Debug) { if (ancestor != null) { System.out.println(" ::--> " + ancestor.getID()); } else { System.out.println(" ::--> NULL"); } System.out.println(" :: ADLSequencer --> END - findCommonAncestor"); } return ancestor; } /** * Retrieve the activity (<code>SeqActivity</code>) with the associated * activity ID. * * @param iActivityID * ID of the desired activity. * * @return The activity (<code>SeqActivity</code>) with the associated * activity ID, or <code>null</code> if it does not exist. */ private SeqActivity getActivity(String iActivityID) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - getActivity"); System.out.println(" ::--> " + iActivityID); } SeqActivity thisActivity = null; if (mSeqTree != null) { // Get an activity node from the activity tree based on its ID thisActivity = mSeqTree.getActivity(iActivityID); if (_Debug) { System.out.println(" ::--> FOUND"); System.out.println(" :: ADLSequencer --> END - getActivity"); } } else { if (_Debug) { System.out.println(" ::--> ERROR : No Activity Tree"); System.out.println(" :: ADLSequencer --> END - getActivity"); } } return thisActivity; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Rollup Behavior -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * Gets the current active activity tree. * * @return The current activity tree (<code>SeqActivityTree</code>). */ public ISeqActivityTree getActivityTree() { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - getActivityTree"); System.out.println(" :: ADLSequencer --> END - getActivityTree"); } return mSeqTree; } /** * Displays the values of the <code>ADLTOC</code> objects that constitute * table of contents. This method is used for diagnostic purposes. * * @param iOldTOC * A List of <code>ADLTOC</code> objects describing the * 'first pass' TOC. * * @param oNewTOC * A List of <code>ADLTOC</code> objects describing the * 'final pass' TOC. * * @return The set of valid activity IDs for 'Choice' navigation requests. */ private Hashtable<String, ActivityNode> getChoiceSet(TreeModel treeModel) { Hashtable<String, ActivityNode> set = null; String lastLeaf = null; if (treeModel != null) { ActivityNode tempNode = null; set = new Hashtable<String, ActivityNode>(); ActivityNode rootNode = (ActivityNode) treeModel.getRoot(); if (rootNode != null) { @SuppressWarnings("unchecked") Enumeration<ActivityNode> breadthFirst = rootNode.breadthFirstEnumeration(); List<ActivityNode> bfList = Collections.list(breadthFirst); // Traverse the breadth-first search backwards for (int i = bfList.size() - 1; i > 0; i--) { tempNode = bfList.get(i); if (tempNode.getDepth() == -1) { if (tempNode.isSelectable()) { set.put(tempNode.getActivity().getID(), tempNode); } } else if (!tempNode.isHidden()) { set.put(tempNode.getActivity().getID(), tempNode); } if (lastLeaf == null) { if (tempNode.isLeaf() && tempNode.isEnabled()) { lastLeaf = tempNode.getActivity().getID(); } } } } } if (lastLeaf != null) { if (_Debug) { System.out.println(" ::--> Setting last leaf --> " + lastLeaf); } mSeqTree.setLastLeaf(lastLeaf); } // If there are no items in the set, there is no TOC. if (set != null && set.isEmpty()) { set = null; } // TODO: JLR -- think we might be able to live without this... 9/10/2007 // If there is only one item in the set, it must be the root -- remove // it // If there is only one item in the set, it is the parent of a // choiceExit == false cluster, it cannot be selected -- no TOC /*if (oNewTOC.size() == 1) { ADLTOC temp = (ADLTOC) oNewTOC.get(0); if (!temp.mIsEnabled) { if (_Debug) { System.out.println(" ::--> Clearing single non-enabled " + " activity"); } oNewTOC.remove(0); } else if (!temp.mLeaf) { if (_Debug) { System.out.println(" ::--> Clearing root activity"); } oNewTOC.remove(0); } }*/ return set; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Selection and Randomization Behavior -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * Retrieves the set of objective status records associated with an * activity. * * @param iActivityID * The ID of the activity whose objectives are requested. * * @return A <code>List</code> of <code>ADLObjStatus</code> objects * for the requested activity or <code>null</code> if none are * defined. */ public List<ADLObjStatus> getObjStatusSet(String iActivityID) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - getObjStatusSet"); } List<ADLObjStatus> objSet = null; SeqActivity act = getActivity(iActivityID); // Make sure the activity exists if (act != null) { // Ask the activity for its current set of objective status records objSet = act.getObjStatusSet(); } else { if (_Debug) { System.out.println(" ::--> Activity not found"); } } if (_Debug) { if (objSet == null) { System.out.println(" ::--> NULL"); } else { System.out.println(" ::--> [ " + objSet.size() + " ]"); } System.out.println(" :: ADLSequencer --> END - getObjStatusSet"); } return objSet; } /** * Gets the root of the current activity tree. * * @return The root activity of the current activity tree (<code>SeqActivity</code>). */ public ISeqActivity getRoot() { ISeqActivity rootActivity = null; if (mSeqTree != null) { rootActivity = mSeqTree.getRoot(); } return rootActivity; } /** * Method added 8/24/2007 JLR * * This is an attempt to more efficiently generate the TreeModel object that we pass around * instead of the ADLTOC List that ADL uses in their reference implementation. * */ private DefaultTreeModel getTreeModel(SeqActivity iStart) { List<ActivityNode> nodes = new ArrayList<ActivityNode>(); log.debug("Generating the table of contents tree model"); boolean done = false; // Make sure we have an activity tree if (mSeqTree == null) { log.warn("No activity tree found"); done = true; } // Perform a breadth-first walk of the activity tree. SeqActivity walk = iStart; int depth = 0; int parentTOC = -1; List<SeqActivity> lookAt = new ArrayList<SeqActivity>(); List<SeqActivity> flatTOC = new ArrayList<SeqActivity>(); // Tree traversal status indicators boolean next = false; // Make sure the activity has been associated with this sequencer // If not, build the TOC from the root if (walk == null) { walk = mSeqTree.getRoot(); } if (!done) { if (log.isDebugEnabled()) { log.debug(" ::--> Building TOC from: " + walk.getID()); } } SeqActivity cur = (mSeqTree != null ? mSeqTree.getFirstCandidate() : null); int curIdx = -1; if (cur == null) { cur = mSeqTree.getCurrentActivity(); } while (!done) { next = false; ActivityNode node = new ActivityNode(walk); node.setParentLocation(parentTOC); node.setEnabled(!checkActivity(walk)); node.setHidden(!walk.getIsVisible()); node.setLeaf(!walk.hasChildren(false)); // Check to see if this activity can be included -- JLR if (canBeIncluded(walk, cur, node)) { ISeqActivity parent = walk.getParent(); node.setIncluded(true); // Not sure why we're checking this twice... but I'm sticking with the ADL template here -- JLR if (parent != null) { node.setInChoice(parent.getControlModeChoice()); } else { node.setInChoice(true); } node.setDepth(depth); // Check if we looking at the 'current' cluster if (cur != null) { if (walk.getID().equals(cur.getID())) { node.setCurrent(true); curIdx = nodes.size(); } } } else { node.setDepth(-depth); node.setSelectable(false); } // Doesn't really matter if it's included or not, we still add it... strangely enough nodes.add(node); // Add this activity to the "flat TOC" flatTOC.add(walk); // If this activity has children, look at them later... if (walk.hasChildren(false)) { // Remember where we are at and look at the children now, // unless we are at the root if (walk.getParent() != null) { lookAt.add(walk); } // Go to the first child walk = (walk.getChildren(false)).get(0); parentTOC = nodes.size() - 1; depth++; next = true; } if (!next) { // Move to its sibling walk = walk.getNextSibling(false); ActivityNode tempNode = nodes.get(nodes.size() - 1); parentTOC = tempNode.getParentLocation(); while (walk == null && !done) { if (lookAt.size() > 0) { // Walk back up the tree to the parent's next sibling walk = lookAt.get(lookAt.size() - 1); lookAt.remove(lookAt.size() - 1); depth--; // Find the correct parent tempNode = nodes.get(parentTOC); String tempNodeId = tempNode.getActivity().getID(); walk.getID(); while (!tempNodeId.equals(walk.getID())) { parentTOC = tempNode.getParentLocation(); tempNode = nodes.get(parentTOC); tempNodeId = tempNode.getActivity().getID(); } walk = walk.getNextSibling(false); } else { done = true; } } if (walk != null) { parentTOC = tempNode.getParentLocation(); } } } log.debug(" ::--> Completed first pass"); // After the TOC has been created, mark activites unselectable // if the Prevent Activation prevents them being selected, // and mark them invisible if they are descendents of a hidden // from choice activity int hidden = -1; int prevented = -1; for (int i = 0; i < nodes.size(); i++) { SeqActivity tempAct = flatTOC.get(i); ActivityNode tempNode = nodes.get(i); if (log.isDebugEnabled()) { log.debug(" ::--> Evaluating --> " + tempAct.getID()); log.debug(" --> " + tempAct.getTitle()); } int tempDepth = tempNode.getDepth(); // Flipping the cardinality of the hidden depths, apparently -- JLR int checkDepth = ((tempDepth >= 0) ? tempDepth : (-tempDepth)); if (hidden != -1) { // Check to see if we are done hiding activities if (checkDepth <= hidden) { hidden = -1; } else { // This must be a descendent tempNode.setDepth(-depth); tempNode.setSelectable(false); tempNode.setHidden(true); } } // Evaluate hide from choice rules if we are not hidden if (hidden == -1) { // Attempt to get rule information from the activity ISeqRuleset hiddenRules = tempAct.getPreSeqRules(); String result = null; if (hiddenRules != null) { result = hiddenRules.evaluate(SeqRuleset.RULE_TYPE_HIDDEN, tempAct, false); } // If the rule evaluation did not return null, the activity // must be hidden. if (result != null) { // The depth we are looking for should be positive hidden = -tempNode.getDepth(); prevented = -1; } else { if (log.isDebugEnabled()) { log.debug(" ::--> Prevented ??" + prevented); } if (prevented != -1) { // Check to see if we are done preventing activities if (checkDepth <= prevented) { // Reset the check until we find another prevented prevented = -1; } else { // This must be a prevented descendent tempNode.setDepth(-1); tempNode.setSelectable(false); } } else { // Check if this activity is prevented from activation if (tempAct.getPreventActivation() && !tempAct.getIsActive()) { if (cur != null) { if (tempAct != cur && cur.getParent() != tempAct) { if (log.isDebugEnabled()) { log.debug(" ::--> PREVENTED !!"); log.debug(" " + tempAct.getID() + " != " + cur.getParent().getID()); } // Not sure why we need to check this again -- JLR tempNode.setIncluded(false); // Flipping the cardinality of the hidden depths, apparently -- JLR int td = tempNode.getDepth(); prevented = (td > 0) ? td : -td; // The activity cannot be selected tempNode.setDepth(-1); tempNode.setSelectable(false); } } } } } } } if (log.isDebugEnabled()) { log.debug(" ::--> Completed post-1 pass"); } // After the TOC has been created, mark activites unselectable // if the Choice Exit control prevents them being selected SeqActivity noExit = null; if (mSeqTree.getFirstCandidate() != null) { walk = mSeqTree.getFirstCandidate().getParent(); } else { walk = null; } // Walk up the active path looking for a non-exiting cluster while (walk != null && noExit == null) { // We cannot choose any target that is outside of the activiy tree, // so choice exit does not apply to the root of the tree if (walk.getParent() != null) { if (!walk.getControlModeChoiceExit()) { noExit = walk; } } // Move up the tree walk = walk.getParent(); } if (noExit != null) { depth = -1; if (log.isDebugEnabled()) { log.debug(" ::--> Found NoExit Cluster -- " + noExit.getID()); } // Only descendents of this activity can be selected. for (int i = 0; i < nodes.size(); i++) { ActivityNode tempNode = nodes.get(i); int td = tempNode.getDepth(); // When we find the the 'non-exiting' activity, remember its // depth if (tempNode.getActivity().getID().equals(noExit.getID())) { depth = (td > 0) ? td : -td; // The cluster activity cannot be selected tempNode.setDepth(-1); tempNode.setSelectable(false); } // If we haven't found the the 'non-exiting' activity yet, then // the // activity being considered cannot be selected. else if (depth == -1) { tempNode.setDepth(-1); tempNode.setSelectable(false); } // When we back out of the depth-first-walk and encounter a // sibling // or parent of the 'non-exiting' activity, start making // activity // unselectable else if (((td > 0) ? td : -td) <= depth) { depth = -1; tempNode.setDepth(-1); tempNode.setSelectable(false); } } } // Boundary Condition -- evaluate choice exit on root ActivityNode tempNode = nodes.get(0); SeqActivity root = mSeqTree.getRoot(); if (!root.getControlModeChoiceExit()) { tempNode.setSelectable(false); } log.debug(" ::--> Completed second pass"); // Look for constrained activities relative to the current activity and // mark activites unselectable if they are outside of the avaliable set SeqActivity con = null; if (mSeqTree.getFirstCandidate() != null) { walk = mSeqTree.getFirstCandidate().getParent(); } else { walk = null; } // Walk up the tree to the root while (walk != null && con == null) { if (walk.getConstrainChoice()) { con = walk; } walk = walk.getParent(); } // Evaluate constrained choice set if (con != null) { if (log.isDebugEnabled()) { log.debug(" ::--> Constrained Choice Activity Found"); log.debug(" ::--> Stopped at --> " + con.getID()); } int forwardAct = -1; int backwardAct = -1; List<SeqActivity> list = null; Walk walkCon = new Walk(); walkCon.at = con; // Find the next activity relative to the constrained activity. processFlow(FLOW_FORWARD, false, walkCon, true); if (walkCon.at == null) { if (log.isDebugEnabled()) { log.debug(" ::--> Walked forward off the tree"); } walkCon.at = con; } String lookFor = ""; list = walkCon.at.getChildren(false); if (list != null) { int size = list.size(); lookFor = list.get(size - 1).getID(); } else { lookFor = walkCon.at.getID(); } for (int j = 0; j < nodes.size(); j++) { tempNode = nodes.get(j); if (tempNode.getActivity().getID().equals(lookFor)) { forwardAct = j; break; } } // Find the previous activity relative to the constrained activity. walkCon.at = con; processFlow(FLOW_BACKWARD, false, walkCon, true); if (walkCon.at == null) { log.debug(" ::--> Walked backward off the tree"); walkCon.at = con; } lookFor = walkCon.at.getID(); for (int j = 0; j < nodes.size(); j++) { tempNode = nodes.get(j); if (tempNode.getActivity().getID().equals(lookFor)) { backwardAct = j; break; } } // If the forward activity on either end of the range is a cluster, // we need to include its descendents tempNode = nodes.get(forwardAct); if (!tempNode.isLeaf()) { int idx = forwardAct; boolean foundLeaf = false; while (!foundLeaf) { for (int i = nodes.size() - 1; i > idx; i--) { tempNode = nodes.get(i); if (tempNode.getParentLocation() == idx) { idx = i; foundLeaf = tempNode.isLeaf(); break; } } } if (idx != nodes.size()) { forwardAct = idx; } } if (log.isDebugEnabled()) { log.debug(" ::--> Constrained Range == [ " + backwardAct + " , " + forwardAct + " ]"); } // Disable activities outside of the avaliable range for (int i = 0; i < nodes.size(); i++) { tempNode = nodes.get(i); if (i < backwardAct || i > forwardAct) { tempNode.setSelectable(false); if (log.isDebugEnabled()) { log.debug(" ::--> Turn off -- " + tempNode.getActivity().getID()); } } } } log.debug(" ::--> Completed third pass"); // Walk the TOC looking for disabled activities... if (nodes != null) { depth = -1; for (int i = 0; i < nodes.size(); i++) { tempNode = nodes.get(i); if (depth != -1) { int td = tempNode.getDepth(); if (depth >= ((td > 0) ? td : -td)) { depth = -1; } else { tempNode.setEnabled(false); tempNode.setSelectable(false); } } if (!tempNode.isEnabled() && depth == -1) { int td = tempNode.getDepth(); // Remember where the disabled activity is depth = (td > 0) ? td : -td; if (log.isDebugEnabled()) { log.debug(" ::--> [" + i + "] " + "Found Disabled --> " + tempNode.getActivity().getID() + " <<" + tempNode.getDepth() + ">>"); } } } } log.debug(" ::--> Completed fourth pass"); // If there is a current activity, check availablity of its siblings // This pass corresponds to Case #2 of the Choice Sequencing Request if (nodes != null && curIdx != -1) { log.debug(" ::--> Checking Current Activity Siblings"); int par = (nodes.get(curIdx)).getParentLocation(); int idx; // Check if the current activity is in a forward only cluster if (cur.getParent() != null && cur.getParent().getControlForwardOnly()) { idx = curIdx - 1; tempNode = nodes.get(idx); while (tempNode.getParentLocation() == par) { tempNode.setSelectable(false); idx--; tempNode = nodes.get(idx); } } // Check for Stop Forward Traversal Rules idx = curIdx; boolean blocked = false; while (idx < nodes.size()) { tempNode = nodes.get(idx); if (tempNode.getParentLocation() == par) { if (!blocked) { ISeqRuleset stopTrav = tempNode.getActivity().getPreSeqRules(); String result = null; if (stopTrav != null) { result = stopTrav.evaluate(SeqRuleset.RULE_TYPE_FORWARDBLOCK, tempNode.getActivity(), false); } // If the rule evaluation did not return null, the // activity is blocked blocked = (result != null); } else { tempNode.setSelectable(false); } } idx++; } } log.debug(" ::--> Completed fifth pass"); // Evaluate Stop Forward Traversal Rules -- this pass cooresponds to // Case #3 and #5 of the Choice Sequencing Request Subprocess. In these // cases, we need to check if the target activity is forward in the // Activity Tree relative to the commen ancestor and cuurent activity if (nodes != null && curIdx != -1) { log.debug(" ::--> Checking Stop Forward Traversal"); int curParent = (nodes.get(curIdx)).getParentLocation(); int idx = nodes.size() - 1; tempNode = nodes.get(idx); // Walk backward from last available activity, // checking each until we get to a sibling of the current activity while (tempNode.getParentLocation() != -1 && tempNode.getParentLocation() != curParent) { tempNode = nodes.get(tempNode.getParentLocation()); ISeqRuleset stopTrav = tempNode.getActivity().getPreSeqRules(); String result = null; if (stopTrav != null) { result = stopTrav.evaluate(SeqRuleset.RULE_TYPE_FORWARDBLOCK, tempNode.getActivity(), false); } // If the rule evaluation did not return null, // then all of its descendents are blocked if (result != null) { if (log.isDebugEnabled()) { log.debug(" ::--> BLOCKED SOURCE --> " + tempNode.getActivity().getID() + " [" + tempNode.getDepth() + "]"); } // The depth of the blocked activity int blocked = tempNode.getDepth(); for (int i = idx; i < nodes.size(); i++) { ActivityNode tempAN = nodes.get(i); int td = tempAN.getDepth(); int checkDepth = ((td >= 0) ? td : (-td)); // Check to see if we are done blocking activities if (checkDepth <= blocked) { break; } // This activity must be a descendent tempAN.setSelectable(false); } } idx--; tempNode = nodes.get(idx); } } log.debug(" ::--> Completed sixth pass"); // Boundary condition -- if there is a TOC make sure all "selectable" // clusters actually flow into content for (int i = 0; i < nodes.size(); i++) { tempNode = nodes.get(i); if (!tempNode.isLeaf()) { if (log.isDebugEnabled()) { log.debug(" ::--> Process 'Continue' request from " + tempNode.getActivity().getID()); } SeqActivity from = tempNode.getActivity(); // Confirm 'flow' is enabled from this cluster if (from.getControlModeFlow()) { // Begin traversing the activity tree from the root Walk treeWalk = new Walk(); treeWalk.at = from; boolean success = processFlow(ADLSequencer.FLOW_FORWARD, true, treeWalk, false); if (!success) { tempNode.setSelectable(false); if (log.isDebugEnabled()) { log.debug(" :+: CONTINUE FAILED :+: --> " + treeWalk.at.getID()); } } } else { // Cluster does not have flow == true tempNode.setSelectable(false); } } } log.debug(" ::--> Completed seventh pass"); for (int i = nodes.size() - 1; i >= 0; i--) { tempNode = nodes.get(i); if (tempNode.isCurrent() && tempNode.isInChoice()) { if (tempNode.getDepth() < 0) { tempNode.setDepth(-tempNode.getDepth()); } } if (tempNode.getDepth() >= 0) { while (tempNode.getParentLocation() != -1) { tempNode = nodes.get(tempNode.getParentLocation()); if (tempNode.getDepth() < 0) { tempNode.setDepth(-tempNode.getDepth()); } } } else if (!tempNode.isHidden()) { tempNode.setDepth(-1); } } for (int i = 0; i < nodes.size(); i++) { tempNode = nodes.get(i); if (tempNode.isHidden()) { tempNode.setDepth(-1); List<Integer> parents = new ArrayList<Integer>(); for (int j = i + 1; j < nodes.size(); j++) { tempNode = nodes.get(j); if (tempNode.getParentLocation() == i && tempNode.getDepth() > 0) { tempNode.setDepth(tempNode.getDepth() - 1); parents.add(Integer.valueOf(j)); } else { if (tempNode.getDepth() != -1) { int idx = parents.indexOf(Integer.valueOf(tempNode.getParentLocation())); if (idx != -1) { tempNode.setDepth(tempNode.getDepth() - 1); parents.add(Integer.valueOf(j)); } } } } } } log.debug(" ::--> Completed TOC walk up"); log.debug(" :: ADLSequencer --> END - getTOC"); ActivityNode rootNode = null; for (int i = 0; i < nodes.size(); i++) { ActivityNode node = nodes.get(i); if (node.getParentLocation() == -1) { rootNode = node; } else if (node.getDepth() >= 0) { ActivityNode parentNode = nodes.get(node.getParentLocation()); parentNode.add(node); } } DefaultTreeModel treeModel = new DefaultTreeModel(rootNode); return treeModel; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Sequencing Behavior -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * Retrieves the current set of valid navigation requests. * * @param oValid * Upon return, contains the set of valid navigation reqeusts. */ public void getValidRequests(IValidRequests argValid) { ADLValidRequests oValid = (ADLValidRequests) argValid; if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - getValidRequests"); } ADLValidRequests valid = null; if (mSeqTree != null) { valid = mSeqTree.getValidRequests(); if (valid != null) { validateRequests(); valid = mSeqTree.getValidRequests(); } } // Copy the set of valid requests to the return object if (valid != null) { oValid.mContinue = valid.mContinue; oValid.mContinueExit = valid.mContinueExit; oValid.mPrevious = valid.mPrevious; if (valid.mTreeModel != null) { // TODO: Remove this //oValid.mTOC = (List) (((List) (valid.mTOC)).clone()); oValid.mTreeModel = valid.mTreeModel; //convertTOC((List)oValid.mTOC); } if (valid.mChoice != null) { oValid.mChoice = new HashMap<String, ActivityNode>(valid.mChoice); } } else { if (_Debug) { System.out.println(" ::--> ERROR : Unable to validate requests"); } // Make sure nothing is valid oValid.mContinue = false; oValid.mContinueExit = false; oValid.mPrevious = false; oValid.mChoice = null; // TODO: Remove this //oValid.mTOC = null; oValid.mTreeModel = null; } if (_Debug) { if (oValid.mChoice == null) { System.out.println(" ::--> [NULL]"); } else { System.out.println(" ::--> [" + oValid.mChoice.size() + "]"); } System.out.println(" :: ADLSequencer --> END - getValidRequests"); } } /** * Initiates deterministic rollup from the target activity and any other * activities that may have been affected. <br> * <b>Internal Sequencing Process</b><br> * <br> * * @param ioTarget * Identifies the activity where rollup is applied. * * @param iWriteObjIDs * Identifies the set of objective IDs that are affected by this * invokation; or <code>null</code> if none. */ private void invokeRollup(SeqActivity ioTarget, List<String> iWriteObjIDs) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - invokeRollup"); System.out.println(" ::--> Start: " + ioTarget.getID()); } Hashtable<String, Integer> rollupSet = new Hashtable<String, Integer>(); // Case #1 -- Rollup applies along the active path if (ioTarget == mSeqTree.getCurrentActivity()) { if (_Debug) { System.out.println(" ::--> CASE #1 Rollup"); } SeqActivity walk = ioTarget; // Walk from the target to the root, apply rollup rules at each step while (walk != null) { if (_Debug) { System.out.println(" ::--> Adding :: " + walk.getID()); } rollupSet.put(walk.getID(), Integer.valueOf(walk.getDepth())); List<String> writeObjIDs = walk.getObjIDs(null, false); if (writeObjIDs != null) { for (int i = 0; i < writeObjIDs.size(); i++) { String objID = writeObjIDs.get(i); if (_Debug) { System.out.println(" ::--> Rolling up Obj -- " + objID); } // Need to identify all activity's that 'read' this // objective // into their primary objective -- those activities need // to be // included in the rollup set List<String> acts = mSeqTree.getObjMap(objID); if (_Debug) { System.out.println(" ACTS == " + acts); } if (acts != null) { for (int j = 0; j < acts.size(); j++) { SeqActivity act = getActivity(acts.get(j)); if (_Debug) { System.out.println(" *+> " + j + " <+* :: " + act.getID()); } // Only rollup at the parent of the affected // activity act = act.getParent(); if (act != null) { // Only add if the activity is selected if (act.getIsSelected()) { if (_Debug) { System.out.println(" ::--> Adding :: " + act.getID()); } rollupSet.put(act.getID(), Integer.valueOf(act.getDepth())); } } } } } } walk = walk.getParent(); } // Remove the Current Activity from the rollup set rollupSet.remove(ioTarget.getID()); } // Case #2 -- Rollup applies when the state of a global shared objective // is written to... if (iWriteObjIDs != null) { if (_Debug) { System.out.println(" ::--> CASE #2 Rollup"); } for (int i = 0; i < iWriteObjIDs.size(); i++) { String objID = iWriteObjIDs.get(i); if (_Debug) { System.out.println(" ::--> Rolling up Obj -- " + objID); } // Need to identify all activity's that 'read' this objective // into their primary objective -- those activities need to be // included in the rollup set List<String> acts = mSeqTree.getObjMap(objID); if (_Debug) { System.out.println(" ACTS == " + acts); } if (acts != null) { for (int j = 0; j < acts.size(); j++) { SeqActivity act = getActivity(acts.get(j)); if (_Debug) { System.out.println(" *+> " + j + " <+* :: " + act.getID()); } // Only rollup at the parent of the affected activity act = act.getParent(); if (act != null) { // Only add if the activity is selected if (act.getIsSelected()) { if (_Debug) { System.out.println(" ::--> Adding :: " + act.getID()); } rollupSet.put(act.getID(), Integer.valueOf(act.getDepth())); } } } } } } // Perform the deterministic rollup extension while (rollupSet.size() != 0) { if (_Debug) { System.out.println(" ::--> Rollup Set Size == " + rollupSet.size()); for (Entry<String, Integer> entry : rollupSet.entrySet()) { System.out.println(" ::--> " + entry.getKey() + " // " + entry.getValue()); } } // Find the deepest activity SeqActivity deepest = null; int depth = -1; for (Entry<String, Integer> entry : rollupSet.entrySet()) { String key = entry.getKey(); int thisDepth = entry.getValue(); if (depth == -1) { depth = thisDepth; deepest = getActivity(key); } else if (thisDepth > depth) { depth = thisDepth; deepest = getActivity(key); } } if (deepest != null) { doOverallRollup(deepest, rollupSet); // If rollup was performed on the root, set the course's status if (deepest == mSeqTree.getRoot()) { @SuppressWarnings("unused") String completed = "unknown"; if (deepest.getObjStatus(false)) { completed = (deepest.getObjSatisfied(false)) ? "satisfied" : "notSatisfied"; } if (deepest.getObjMeasureStatus(false)) { completed = (new Double(deepest.getObjMeasure(false))).toString(); } if (deepest.getProgressStatus(false)) { completed = (deepest.getAttemptCompleted(false)) ? "completed" : "incomplete"; } //ADLSeqUtilities.setCourseStatus(mSeqTree.getCourseID(), // mSeqTree.getLearnerID(), satisfied, measure, // completed); } } else { if (_Debug) { System.out.println(" :: ERROR :: No activity found"); } } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - invokeRollup"); } } /** * This method determines if an activity is a descendent of another * activity. * * @param iRoot * Root of the subtree being looked at. * * @param iTarget * Target activity being tested. * * @return Returns <code>true</code> if the target is a descendent of the * root, otherwise <code>false</code>. */ private boolean isDescendent(SeqActivity iRoot, SeqActivity iTarget) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - isDescendent"); if (iRoot != null) { System.out.println(" ::--> Root --> " + iRoot.getID()); } else { System.out.println(" ::--> Root --> NULL"); } if (iTarget != null) { System.out.println(" ::--> Target --> " + iTarget.getID()); } else { System.out.println(" ::--> Target --> NULL"); } } boolean found = false; if (iRoot == null) { if (_Debug) { System.out.println(" ::--> ERROR : NULL Root"); } } else if (iRoot == mSeqTree.getRoot()) { // All activities are descendents of the root found = true; } else if (iRoot != null && iTarget != null) { while (iTarget != null && !found) { if (iTarget == iRoot) { found = true; } iTarget = iTarget.getParent(); } } if (_Debug) { System.out.println(" ::--> " + found); System.out.println(" :: ADLSequencer --> END - isDescendent"); } return found; } /** * This method is used to inform the sequencer that a navigation request, * other than 'Choice' has occured. * * @param iRequest * Indicates which navigation request should be processed. * * @return Information about the 'Next' activity to delivery or a processing * error. */ public ADLLaunch navigate(int iRequest) { // This method implements all cases, except case #7 of the Navigation // Request Process (NB.2.1). // // It also applies the Overall Sequencing Process (OP) to the // indicated navigation request. if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - navigate"); System.out.println(" ::--> " + iRequest); } // This function attempts to translate the navigation request into the // corresponding termination and sequencing requests, and invoke the // overall sequencing process. ADLLaunch launch = new ADLLaunch(); // Make sure an activity tree has been associated with this sequencer if (mSeqTree == null) { if (_Debug) { System.out.println(" ::--> ERROR : No activity tree defined."); System.out.println(" :: ADLSequencer --> END - " + "navigate"); } // No activity tree, therefore nothing to do // -- inform the caller of the error. launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; launch.mEndSession = true; return launch; } // If this is a new session, we start at the root. boolean newSession = false; SeqActivity cur = mSeqTree.getCurrentActivity(); if (cur == null) { if (_Debug) { System.out.println(" ::--> No current Activity -- New Session"); } prepareClusters(); newSession = true; validateRequests(); } boolean process = true; ADLValidRequests valid = null; if (newSession && iRequest == SeqNavRequests.NAV_NONE) { if (_Debug) { System.out.println(" ::--> Processing a TOC request"); } } else if (newSession && (iRequest == SeqNavRequests.NAV_EXITALL || iRequest == SeqNavRequests.NAV_ABANDONALL)) { if (_Debug) { System.out.println(" ::--> Exiting a session that hasn't started"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_EXITSESSION; launch.mEndSession = true; process = false; } else if (iRequest == SeqNavRequests.NAV_CONTINUE || iRequest == SeqNavRequests.NAV_PREVIOUS) { validateRequests(); valid = mSeqTree.getValidRequests(); // Can't validate requests -- Error if (valid == null) { if (_Debug) { System.out.println(" ::--> ERROR : " + "Cannot validate request"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; launch.mEndSession = true; // Invalid request -- do not process process = false; } else { if (iRequest == SeqNavRequests.NAV_CONTINUE) { if (!valid.mContinue) { if (_Debug) { System.out.println(" ::--> Continue not valid"); } process = false; launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; } } else { if (!valid.mPrevious) { if (_Debug) { System.out.println(" ::--> Previous not valid"); } process = false; launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; } } } } else { // Use the IMS Navigation Request Process to validate the request process = doIMSNavValidation(iRequest); if (!process) { launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; } } // Process any pending navigation request if (process) { // This block implements the overall sequencing loop // Clear Global State mValidTermination = true; mValidSequencing = true; String seqReq = null; String delReq = null; // Translate the navigation request into termination and/or // sequencing // request(s). switch (iRequest) { case SeqNavRequests.NAV_START: delReq = doSequencingRequest(ADLSequencer.SEQ_START); if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_RESUMEALL: delReq = doSequencingRequest(ADLSequencer.SEQ_RESUMEALL); if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_CONTINUE: if (cur.getIsActive()) { // Issue a termination request of 'exit' seqReq = doTerminationRequest(ADLSequencer.TER_EXIT, false); } if (mValidTermination) { // Issue the pending sequencing request if (seqReq == null) { delReq = doSequencingRequest(ADLSequencer.SEQ_CONTINUE); } else { delReq = doSequencingRequest(seqReq); } } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_PREVIOUS: if (cur.getIsActive()) { // Issue a termination request of 'exit' seqReq = doTerminationRequest(ADLSequencer.TER_EXIT, false); } if (mValidTermination) { // Issue the pending sequencing request if (seqReq == null) { delReq = doSequencingRequest(ADLSequencer.SEQ_PREVIOUS); } else { delReq = doSequencingRequest(seqReq); } } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_ABANDON: // Issue a termination request of 'abandon' seqReq = doTerminationRequest(ADLSequencer.TER_ABANDON, false); // The termination process cannot return a sequencing request // because post condition rules are not evaluated. if (mValidTermination) { if (seqReq != null) { if (_Debug) { System.out.println(" ::--> ERROR : " + "Postconditions Processed"); } } delReq = doSequencingRequest(ADLSequencer.SEQ_EXIT); // If the session hasn't ended, re-validate nav requests if (!mEndSession && !mExitCourse) { if (_Debug) { System.out.println(" ::--> REVALIDATE"); } validateRequests(); } } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_ABANDONALL: // Issue a termination request of 'abandonAll' seqReq = doTerminationRequest(ADLSequencer.TER_ABANDONALL, false); // The termination process cannot return a sequencing request // because post condition rules are not evaluated. if (mValidTermination) { if (seqReq != null) { if (_Debug) { System.out.println(" ::--> ERROR : " + "Postconditions Processed"); } } delReq = doSequencingRequest(ADLSequencer.SEQ_EXIT); } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_SUSPENDALL: // Issue a termination request of 'suspendAll' seqReq = doTerminationRequest(ADLSequencer.TER_SUSPENDALL, false); // The termination process cannot return a sequencing request // because post condition rules are not evaluated. if (mValidTermination) { if (seqReq != null) { if (_Debug) { System.out.println(" ::--> ERROR : " + "Postconditions Processed"); } } delReq = doSequencingRequest(ADLSequencer.SEQ_EXIT); } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_EXIT: // Issue a termination request of 'exit' seqReq = doTerminationRequest(ADLSequencer.TER_EXIT, false); if (mValidTermination) { if (seqReq == null) { delReq = doSequencingRequest(ADLSequencer.SEQ_EXIT); } else { delReq = doSequencingRequest(seqReq); } // If the session hasn't ended, re-validate nav requests if (!mEndSession && !mExitCourse) { if (_Debug) { System.out.println(" ::--> REVALIDATE"); } validateRequests(); } } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_EXITALL: // Issue a termination request of 'exitAll' seqReq = doTerminationRequest(ADLSequencer.TER_EXITALL, false); // The termination process cannot return a sequencing request // because post condition rules are not evaluated. if (mValidTermination) { if (seqReq != null) { if (_Debug) { System.out.println(" ::--> ERROR : " + "Postconditions Processed"); } } delReq = doSequencingRequest(ADLSequencer.SEQ_EXIT); } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } break; case SeqNavRequests.NAV_NONE: // Don't invoke any termination or sequencing requests, // but display a TOC if available launch.mSeqNonContent = ADLLaunch.LAUNCH_TOC; launch.mNavState = mSeqTree.getValidRequests(); // Make sure that a TOC is realy available // TODO: Moved from mTOC to mTreeModel if (launch.mNavState.mTreeModel == null) { launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; } break; default: if (_Debug) { System.out.println(" ::--> ERROR : " + "Invalid navigation request: " + iRequest); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; } } else { if (_Debug) { System.out.println(" ::--> INVALID NAV REQUEST"); } launch.mNavState = mSeqTree.getValidRequests(); // If navigation requests haven't been validated, try to validate // now. if (launch.mNavState == null) { if (_Debug) { System.out.println(" ::--> Not Validated Yet -- DO IT NOW"); } validateRequests(); launch.mNavState = mSeqTree.getValidRequests(); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - navigate"); } return launch; } /** * This method is used to inform the sequencer that a 'Choice' navigation * request has occured. * * @param iTarget * ID (<code>String</code>) of the target activity. * * @return Information about the 'Next' activity to delivery or a processing * error. */ public ADLLaunch navigate(String iTarget) { // This method implements case 7 of the Navigation Request Process // (NB.2.1). // // It also applies the Overall Sequencing Process (OP) to the // indicated navigation request. if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - navigate[choice]"); System.out.println(" :: [" + iTarget + "]"); } ADLLaunch launch = new ADLLaunch(); // Make sure an activity tree has been associated with this sequencer if (mSeqTree == null) { if (_Debug) { System.out.println(" ::--> ERROR : No activity tree defined."); System.out.println(" :: ADLSequencer --> END - " + "navigate[choice]"); } // No activity tree, therefore nothing to do // -- inform the caller of the error. launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; launch.mEndSession = true; return launch; } // Make sure the requested activity exists ISeqActivity target = getActivity(iTarget); if (target != null) { // If this is a new session, we start at the root. boolean newSession = false; SeqActivity cur = mSeqTree.getCurrentActivity(); if (cur == null) { prepareClusters(); newSession = true; } boolean process = true; validateRequests(); // If the sequencing session has already begun, confirm the // navigation request is valid. if (!newSession) { ADLValidRequests valid = mSeqTree.getValidRequests(); if (valid != null) { // Confirm the target activity is allowed if (valid.mChoice != null) { ActivityNode testNode = valid.mChoice.get(iTarget); //ADLTOC test = (ADLTOC) valid.mChoice.get(iTarget); if (testNode == null) { if (_Debug) { System.out.println(" ::--> Target not available"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; process = false; } else if (!testNode.isSelectable()) { if (_Debug) { System.out.println(" ::--> Target not selectable"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; process = false; } } else { if (_Debug) { System.out.println(" ::--> No 'choice' enabled"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR_INVALIDNAVREQ; process = false; } } else { if (_Debug) { System.out.println(" ::--> ERROR : " + "Cannot validate request"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; launch.mEndSession = true; // Invalid request -- do not process process = false; } } // If the navigation request is valid process it if (process) { // This block implements the overall sequencing loop // Clear Global State mValidTermination = true; mValidSequencing = true; String seqReq = iTarget; String delReq = null; // Check if a termination is required if (!newSession) { if (cur.getIsActive()) { // Issue a termination request of 'exit' seqReq = doTerminationRequest(ADLSequencer.TER_EXIT, false); if (seqReq == null) { seqReq = iTarget; } } } if (mValidTermination) { // Issue the pending sequencing request delReq = doSequencingRequest(seqReq); } else { if (_Debug) { System.out.println(" ::--> Invalid Termination"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_NOTHING; } if (mValidSequencing) { doDeliveryRequest(delReq, false, launch); } else { if (_Debug) { System.out.println(" ::--> Invalid Sequencing"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_SEQ_BLOCKED; } } else { if (_Debug) { System.out.println(" ::--> Bad Request"); } launch.mNavState = mSeqTree.getValidRequests(); } } else { if (_Debug) { System.out.println(" ::--> ERROR : The target activity is " + "not in the tree"); } launch.mSeqNonContent = ADLLaunch.LAUNCH_ERROR; launch.mEndSession = true; } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "navigate[choice]"); } return launch; } /** * Walk the activity tree for the first time preparing clusters by applying * selection and randomization processes where appropriate. */ private void prepareClusters() { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - prepareClusters"); } SeqActivity walk = mSeqTree.getRoot(); List<SeqActivity> lookAt = new ArrayList<SeqActivity>(); if (walk != null) { while (walk != null) { // Only prepare clusters if (walk.hasChildren(true)) { if (!walk.getSelectionTiming().equals(SeqActivity.TIMING_NEVER)) { if (!walk.getSelection()) { doSelection(walk); walk.setSelection(true); } } if (!walk.getRandomTiming().equals(SeqActivity.TIMING_NEVER)) { if (!walk.getRandomized()) { doRandomize(walk); walk.setRandomized(true); } } // Keep track of children we still need to look at if (walk.hasChildren(false)) { lookAt.add(walk); } } // Move to next activity walk = walk.getNextSibling(false); if (walk == null) { if (lookAt.size() != 0) { walk = lookAt.get(0); walk = walk.getChildren(false).get(0); lookAt.remove(0); } } } } else { if (_Debug) { System.out.println(" ERROR :: NULL Activity Tree"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - prepareClusters"); } } /** * Traverses the activity tree as defined by the Flow Subprocess. * * @param iDirection * Indicates the direction the tree is to be traversed. * * @param iEnter * Indicates if the children of the starting activity should be * considered during the traversal. * * @param ioFrom * Indicates where the traversal should start at, and upon * completion, indicates where the traversal stopped. * * @param iConChoice * Indicates if only the 'next' activity should be reached. * * @return Indicates if the traversal was successful. */ private boolean processFlow(int iDirection, boolean iEnter, Walk ioFrom, boolean iConChoice) { // This method implements Flow Subprocess SB.2.3 if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "processFlow"); System.out.println(" ::--> " + iDirection); System.out.println(" ::--> " + iConChoice); System.out.println(" ::--> Con Choice? " + (iConChoice ? "Yes" : "No")); if (ioFrom.at != null) { System.out.println(" ::--> " + ioFrom.at.getID()); } else { System.out.println(" ::--> ERROR : NULL starting point"); } } boolean success = true; SeqActivity candidate = ioFrom.at; // Make sure we have somewhere to start from if (candidate != null) { Walk walk = walkTree(iDirection, ADLSequencer.FLOW_NONE, iEnter, candidate, !iConChoice); if (!iConChoice && walk.at != null) { ioFrom.at = walk.at; success = walkActivity(iDirection, ADLSequencer.FLOW_NONE, ioFrom); } else { if (iConChoice) { ioFrom.at = walk.at; if (_Debug) { System.out.println(" ::--> Constrained Choice test"); } } else { if (_Debug) { System.out.println(" ::--> No 'next' activity"); } } success = false; } // Check to see if the sequencing session is ending due to // walking off the activity tree if (walk.at == null && walk.endSession) { if (_Debug) { System.out.println(" ::--> ENDING SESSION"); } // End the attempt on the root of the activity tree terminateDescendentAttempts(mSeqTree.getRoot()); // The sequencing session is over -- set global state mEndSession = true; success = false; } } else { success = false; } if (_Debug) { System.out.println(" ::--> " + success); if (ioFrom.at != null) { System.out.println(" ::--> " + ioFrom.at.getID()); } else { System.out.println(" ::--> NULL"); } System.out.println(" :: ADLSequencer --> END - " + "processFlow"); } return success; } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Delivery Behavior -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * This method is used to inform the sequencer of the suspended state for * the current activity. This state will take affect when the activity * terminates. * * @param iID * ID of the activity whose suspended state is being set. * * @param iSuspended * Indicates if the activity is suspended (<code>true * </code>) * or not (<code>false</code>). */ public void reportSuspension(String iID, boolean iSuspended) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - reportSuspension"); System.out.println(" ::--> Target activity: " + iID); System.out.println(" ::--> " + iSuspended); } SeqActivity target = getActivity(iID); // Make sure the target activity is valid if (target != null) { // Confirm the activity is still active if (target.getIsActive()) { // If the activity is a leaf and is the current activity if (!target.hasChildren(false) && mSeqTree.getCurrentActivity() == target) { // Set the activity's suspended state target.setIsSuspended(iSuspended); } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Target not active"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Activity does not exist"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - reportSuspension"); } } /** * Sets the active activity tree for this sequencer to act on. * * @param iTree * The activty tree for this sequencer to act on. */ public void setActivityTree(ISeqActivityTree iTree) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - setActivityTree"); } // Make sure the activity tree exists. if (iTree != null) { // Set the activity tree to be acted upon mSeqTree = (SeqActivityTree) iTree; if (_Debug) { System.out.println(" ::--> Activity tree set."); } } else { if (_Debug) { System.out.println(" ::--> NULL activity tree."); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - setActivityTree"); } } /** * This method is used to inform the sequencer of a change to an activity's * current attempt experienced duration. * * @param iID * ID of the activity being affected. * * @param iDur * Indicates the experienced duration of the current attempt. * */ public void setAttemptDuration(String iID, IDuration argDur) { ADLDuration iDur = (ADLDuration) argDur; if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "setAttemptDuration"); System.out.println(" ::--> " + iID); if (iDur != null) { System.out.println(" ::--> " + iDur.format(IDuration.FORMAT_SCHEMA)); } else { System.out.println(" ::--> NULL"); } } SeqActivity target = getActivity(iID); // Make sure the activity exists if (target != null) { // Make sure the activity is a valid target for status changes // -- the tracked active leaf current activity if (target.getIsActive() && target.getIsTracked()) { // If the activity is a leaf and is the current activity if (!target.hasChildren(false) && mSeqTree.getCurrentActivity() == target) { target.setCurAttemptExDur(iDur); // Revalidate the navigation requests validateRequests(); } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Target not active"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Activity does not exist"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "setAttemptDuration"); } } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Utility Processes -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * This method is used to inform the sequencer of a change to one of the * activity's objective's measures. * * @param iID * ID of the activity whose measure has changed. * * @param iObjID * ID of the objective whose measure has changed. * * @param iMeasure * New value for the objective's measure. * */ public void setAttemptObjMeasure(String iID, String iObjID, double iMeasure) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "setAttemptObjMeasure"); System.out.println(" ::--> Target activity: " + iID); System.out.println(" ::--> Objective: " + iObjID); System.out.println(" ::--> Measure: " + iMeasure); } // Find the target activty SeqActivity target = getActivity(iID); // Make sure the activity exists if (target != null) { // Make sure the activity is a valid target for status changes // -- the tracked active leaf current activity if (target.getIsActive() && target.getIsTracked()) { // If the activity is a leaf and is the current activity if (!target.hasChildren(false) && mSeqTree.getCurrentActivity() == target) { /* boolean statusChange = */ target.setObjMeasure(iObjID, iMeasure); if (true /* statusChange */) { // If the activity's status changed, it may affect other // activities -- invoke rollup target.getObjIDs(iObjID, false); // Revalidate the navigation requests validateRequests(); } } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Target not active"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Activity does not exist"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "setAttemptObjMeasure"); } } /** * This method is used to inform the sequencer of a change to one of the * activity's objective's satisfaction status. * * @param iID * ID of the activity whose status has changed. * * @param iObjID * ID of the objective whose satisfaction has changed. * * @param iStatus * New value for the objective's satisfaction status. Valid * values are 'unknown', 'satisfied, 'notsatisfied'. * */ public void setAttemptObjSatisfied(String iID, String iObjID, String iStatus) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "setAttemptObjSatisfied"); System.out.println(" ::--> Target activity: " + iID); System.out.println(" ::--> Objective: " + iObjID); System.out.println(" ::--> Status: " + iStatus); } // Find the activty whose status is being set SeqActivity target = getActivity(iID); // Make sure the activity exists if (target != null) { // Make sure the activity is a valid target for status changes // -- the tracked active leaf current activity if (target.getIsActive() && target.getIsTracked()) { // If the activity is a leaf and is the current activity if (!target.hasChildren(false) && mSeqTree.getCurrentActivity() == target) { boolean statusChange = target.setObjSatisfied(iObjID, iStatus); if (statusChange) { target.getObjIDs(iObjID, false); // Revalidate the navigation requests validateRequests(); } } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Target not active"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Activity does not exist"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "setAttemptObjSatisfied"); } } /** * This method is used to inform the sequencer of a change to the activity's * progress status. * * @param iID * ID of the activity whose progress status has changed. * * @param iProgress * New value for the activity's progress status. Valid values * are: 'unknown', 'completed', 'incomplete'. * */ public void setAttemptProgressStatus(String iID, String iProgress) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "setAttemptProgressStatus"); System.out.println(" ::--> Target activity: " + iID); System.out.println(" ::--> Completion status: " + iProgress); } SeqActivity target = getActivity(iID); // Make sure the activity exists if (target != null) { // Make sure the activity is a valid target for status changes // -- the tracked active leaf current activity if (target.getIsActive() && target.getIsTracked()) { // If the activity is a leaf and is the current activity if (!target.hasChildren(false) && mSeqTree.getCurrentActivity() == target) { boolean statusChange = target.setProgress(iProgress); if (statusChange) { // If the activity's status changed, it may affect other // activities -- invoke rollup // invokeRollup(target, null); // Revalidate the navigation requests validateRequests(); } } else { if (_Debug) { System.out.println(" ::--> ERROR : Invalid target"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Target not active"); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Activity does not exist"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "setAttemptProgressStatus"); } } /** * Indicates if a retry sequencing request is being processed. If it is, * this flag indicates to the sequencer to assume 'default' status * information when evaluating sequencing information. * * @param iRetry * Indicates if the sequencer is processing a retry request. */ private void setRetry(boolean iRetry) { mRetry = false; // iRetry; if (_Debug) { System.out.println(" ::--> RETRY == " + mRetry); } } /** * End the attempt on active descendents of the target. * * @param iTarget * Activity for which all descendent attempts will end. */ private void terminateDescendentAttempts(SeqActivity iTarget) { // This is an implementation of the Terminate Descendent Attempts // Process (UP.3) if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "terminateDescendentAttempts"); if (iTarget != null) { System.out.println(" ::--> Target: " + iTarget.getID()); } else { System.out.println(" ::--> ERROR : NULL Activity"); } } SeqActivity cur = mSeqTree.getFirstCandidate(); if (cur != null) { ISeqActivity common = findCommonAncestor(cur, iTarget); SeqActivity walk = cur; while (walk != common) { endAttempt(walk, false); walk = walk.getParent(); } } else { if (_Debug) { System.out.println(" ::--> No current activity"); } } if (_Debug) { System.out.println(" :: ADLSequencer --> END - " + "terminateDescendentAttempts"); } } /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Methods to facilitate navigation and UI -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/ /** * Performs 'what-if' evaluations of possible sequencing requests * originating from the current activity, given the current state of the * activity tree. * * <br> * <br> * This method assumes that content has already reported status and rollup * has been performed. */ private void validateRequests() { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - validateRequests"); } ADLValidRequests valid = mSeqTree.getValidRequests(); // If there is no current activity or the current activity is inactive, // no state change could have occured since the last validation. SeqActivity cur = mSeqTree.getCurrentActivity(); if (cur != null) { boolean test = false; valid = new ADLValidRequests(); ADLLaunch tempLaunch = new ADLLaunch(); // Clear global state mValidTermination = true; mValidSequencing = true; String delReq = null; // If there is a current activity, 'suspendAll' is valid valid.mSuspend = true; // If the current activity does not prevent choiceExit, // Test all 'Choice' requests if (cur.getControlModeChoiceExit() || !cur.getIsActive()) { // TODO: Remove this //valid.mTOC = getTOC(mSeqTree.getRoot()); valid.mTreeModel = getTreeModel(mSeqTree.getRoot()); //convertTOC(valid.mTOC); } if (valid.mTreeModel != null) { valid.mChoice = getChoiceSet(valid.mTreeModel); } if (valid.mTreeModel != null && valid.mTreeModel.getRoot() != null && ((ActivityNode) valid.mTreeModel.getRoot()).getChildCount() == 0) { valid.mTreeModel = null; } // TODO: Remove this. /*if (valid.mTOC != null) { List newTOC = new ArrayList(); valid.mChoice = getChoiceSet((List) valid.mTOC, newTOC); if (newTOC.size() > 0) { valid.mTOC = newTOC; //valid.mTreeModel = convertTOC(newTOC); } else { valid.mTOC = null; valid.mTreeModel = null; } }*/ if (cur.getParent() != null) { if (_Debug) { System.out.println(" ::--> Validate 'Continue'"); } // Always provide a Continue Button if the current activity // is in a 'Flow' cluster if (cur.getParent().getControlModeFlow()) { valid.mContinue = true; } if (_Debug) { System.out.println(" ::--> Validate 'Previous'"); } test = doIMSNavValidation(SeqNavRequests.NAV_PREVIOUS); if (test) { // Test the 'Previous' request mValidSequencing = true; delReq = doSequencingRequest(ADLSequencer.SEQ_PREVIOUS); if (mValidSequencing) { valid.mPrevious = doDeliveryRequest(delReq, true, tempLaunch); } } } } else { if (_Debug) { System.out.println(" ::--> No current activity"); } valid = new ADLValidRequests(); if (_Debug) { System.out.println(" ::--> Validate 'Start' and 'Resume'"); } // Check to see if a resume All should be processed instead of a // start if (mSeqTree.getSuspendAll() != null) { valid.mResume = true; } else { // Test Start Navigation Request Walk walk = new Walk(); walk.at = mSeqTree.getRoot(); valid.mStart = processFlow(ADLSequencer.FLOW_FORWARD, true, walk, false); // Validate availablity of the identfied activity if one was // identified if (valid.mStart) { boolean ok = true; while (walk.at != null && ok) { if (_Debug) { System.out.println(" ::--> Checking --> " + walk.at.getID()); } ok = !checkActivity(walk.at); if (ok) { walk.at = walk.at.getParent(); } else { valid.mStart = false; } } } } if (_Debug) { if (valid.mStart) { System.out.println(" ::--> 'Start' Request is VALID"); } else { System.out.println(" ::--> 'Start' Request is INVALID"); } if (valid.mResume) { System.out.println(" ::--> 'Resume All' Request is VALID"); } else { System.out.println(" ::--> 'Resume All' Request is INVALID"); } } if (_Debug) { System.out.println(" ::--> Validate 'Choice' requests"); } // Test all 'Choice' requests // TODO: Remove this //valid.mTOC = getTOC(mSeqTree.getRoot()); valid.mTreeModel = getTreeModel(mSeqTree.getRoot()); //convertTOC(valid.mTOC); if (valid.mTreeModel != null) { valid.mChoice = getChoiceSet(valid.mTreeModel); } if (valid.mTreeModel != null && valid.mTreeModel.getRoot() != null && ((ActivityNode) valid.mTreeModel.getRoot()).getChildCount() == 0) { valid.mTreeModel = null; } // TODO: Remove this /*if (valid.mTOC != null) { List newTOC = new ArrayList(); valid.mChoice = getChoiceSet((List) valid.mTOC, newTOC); if (newTOC.size() > 0) { valid.mTOC = newTOC; //valid.mTreeModel = convertTOC(newTOC); } else { valid.mTOC = null; valid.mTreeModel = null; } }*/ } // If an updated set of valid requests has completed, associated it with // the activity tree if (valid != null) { mSeqTree.setValidRequests(valid); } if (_Debug) { System.out.println(" :: ADLSequencer --> END - validateRequests"); } } /*private Hashtable getChoiceSet(List iOldTOC, List oNewTOC) { if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - getChoiceSet"); if (iOldTOC != null) { System.out.println(" ::--> " + iOldTOC.size()); } else { System.out.println(" ::--> NULL"); } } Hashtable set = null; String lastLeaf = null; if (iOldTOC != null) { ADLTOC temp = null; set = new Hashtable(); // Walk backward along the List looking for the last available // leaf for (int i = iOldTOC.size() - 1; i >= 0; i--) { temp = (ADLTOC) iOldTOC.get(i); if (temp.mDepth == -1) { if (_Debug) { System.out.println(" ::--> Remove :: " + temp.mID); } if (temp.mIsSelectable) { if (_Debug) { System.out.println(" ::--> Invisible :: " + temp.mID); } // Not in the TOC, but still a valid target set.put(temp.mID, temp); } } else if (temp.mIsVisible) { set.put(temp.mID, temp); oNewTOC.add(temp); } if (lastLeaf == null) { if (temp.mLeaf && temp.mIsEnabled) { lastLeaf = temp.mID; } } } } if (lastLeaf != null) { if (_Debug) { System.out.println(" ::--> Setting last leaf --> " + lastLeaf); } mSeqTree.setLastLeaf(lastLeaf); } // If there are no items in the set, there is no TOC. if (set.size() == 0) { set = null; } // If there is only one item in the set, it must be the root -- remove // it // If there is only one item in the set, it is the parent of a // choiceExit == false cluster, it cannot be selected -- no TOC if (oNewTOC.size() == 1) { ADLTOC temp = (ADLTOC) oNewTOC.get(0); if (!temp.mIsEnabled) { if (_Debug) { System.out.println(" ::--> Clearing single non-enabled " + " activity"); } oNewTOC.remove(0); } else if (!temp.mLeaf) { if (_Debug) { System.out.println(" ::--> Clearing root activity"); } oNewTOC.remove(0); } } if (_Debug) { if (set != null) { System.out.println(" ::--> " + set.size() + " // " + oNewTOC.size()); for (int i = 0; i < oNewTOC.size(); i++) { ADLTOC temp = (ADLTOC) oNewTOC.get(i); temp.dumpState(); } } else { System.out.println(" ::--> NULL"); } System.out.println(" :: ADLSequencer --> END - getChoiceSet"); } return set; } private MutableTreeNode addNode(Map<Integer, MutableTreeNode> nodeMap, SeqActivity activity) { Integer i = Integer.valueOf(activity.getCount()); if (!nodeMap.containsKey(i)) { MutableTreeNode node = new DefaultMutableTreeNode(activity); nodeMap.put(i, node); } return nodeMap.get(i); } private MutableTreeNode addNode(Map<Integer, MutableTreeNode> nodeMap, MutableTreeNode node) { SeqActivity activity = (SeqActivity)((DefaultMutableTreeNode)node).getUserObject(); Integer i = Integer.valueOf(activity.getCount()); if (!nodeMap.containsKey(i)) { nodeMap.put(i, node); } return nodeMap.get(i); } private MutableTreeNode getNode(Map<Integer, MutableTreeNode> nodeMap, Integer key) { return nodeMap.get(key); } private MutableTreeNode nodify(ISeqActivity activity, boolean all) { MutableTreeNode node = new DefaultMutableTreeNode(activity); List<ISeqActivity> children = ((SeqActivity)activity).getChildren(all); if (null != children) { for (ISeqActivity child : children) { MutableTreeNode childNode = nodify(child, all); ((DefaultMutableTreeNode)node).add(childNode); } } return node; } private int addChildren(DefaultMutableTreeNode node, List copy){ SeqActivity activity = (SeqActivity)node.getUserObject(); int count = activity.getCount(); int found = 0; log.info("Node (" + activity.getTitle() +")"); log.info("Remaining items ("+ copy.size() +")"); // // find any objects that have this node's number as parent (eg, tocObject.getParent()+1==this.count) // .... make a new node for it, add it to this node, then call this method with that new node // for(int i=copy.size()-1;i>=0;i-=(found+1)){ /// original TOC objects build with higher order items last ADLTOC t = (ADLTOC)copy.get(i); if(t.mParent+1 == count){ log.info("Adding subnode (" + t.mID + ") to (" + activity.getTitle() +")"); SeqActivity newActivity = mSeqTree.getActivity(t.mID); ActivityNode newNode = new ActivityNode(newActivity); node.add(newNode); found++; copy.remove(i--); found += addChildren(newNode, copy); } } ///Now, do the same for all the children of this node for(Enumeration<ActivityNode> children = node.children();children.hasMoreElements();) { ActivityNode child = children.nextElement(); found += addChildren(child, copy); } return found; } private TreeModel convertTOC(List<ADLTOC> tocList) { ActivityNode root = null; List copy = new ArrayList(tocList); ADLTOC temp; // Get the root for (int i=0;i<copy.size();++i){ temp = (ADLTOC)copy.get(i); if(temp.mParent == -1){ ///presumes that there is only one 'root' SeqActivity activity = mSeqTree.getActivity(temp.mID); root = new ActivityNode(activity); copy.remove(i); break; } } // Traverse the tree while there are still outstanding objects && while there is still progress for(int lastrun=0, thisrun = -1;copy.size()>0 && lastrun!=thisrun;lastrun=thisrun,thisrun=-1){ thisrun = addChildren(root, copy); } root.sortChildrenRecursively(); //fixTreeNodes(root); TreeModel treeModel = new DefaultTreeModel(root); return treeModel; }*/ /* * This is a method inserted for the Sakai SCORM implementation * * */ /*public TreeModel getTreeModel(ISeqActivity iStart) { Map<Integer, MutableTreeNode> nodeMap = new HashMap<Integer, MutableTreeNode>(); List lookAt = new ArrayList(); boolean done = false; // Make sure we have an activity tree if (mSeqTree == null) { if (_Debug) { System.out.println(" ::--> No Activity Tree"); } done = true; } // Perform a breadth-first walk of the activity tree. SeqActivity walk = (SeqActivity)iStart; int depth = 0; // Tree traversal status indicators boolean next = false; boolean include = false; boolean collapse = false; // Make sure the activity has been associated with this sequencer // If not, build the TOC from the root if (walk == null) { walk = mSeqTree.getRoot(); } MutableTreeNode startNode = nodify(walk, true); return new DefaultTreeModel(startNode); }*/ /** * Traverses the activity tree as defined by the Flow Activity Traveral * Subprocess. * * @param iDirection * Indicates the direction the tree is to be traversed. * * @param iPrevDirection * Indicates the previous traversal direction. * * @param ioFrom * Indicates where the traversal should start at, an upon * completion, indicates where the traversal stopped. * * @return Indicates if the traversal resulted in a deliveriable activity. */ private boolean walkActivity(int iDirection, int iPrevDirection, Walk ioFrom) { // This method implements Flow Subprocess SB.2.3 if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "walkActivity"); System.out.println( " Dir ::--> " + (iDirection == ADLSequencer.FLOW_BACKWARD ? "Backward" : "Forward")); if (iPrevDirection == ADLSequencer.FLOW_NONE) { System.out.println(" Prev ::--> None"); } else { System.out.println( " Prev ::--> " + (iPrevDirection == ADLSequencer.FLOW_BACKWARD ? "Backward" : "Forward")); } if (ioFrom.at != null) { System.out.println(" ::--> " + ioFrom.at.getID()); } else { System.out.println(" ::--> ERROR : NULL starting point"); } } boolean deliver = true; SeqActivity parent = ioFrom.at.getParent(); if (parent != null) { // Confirm that 'flow' is enabled for the cluster if (!parent.getControlModeFlow()) { if (_Debug) { System.out.println(" ::--> Control Mode violated"); } deliver = false; } } else { if (_Debug) { System.out.println(" ::--> ERROR : Cannot have null parent"); } deliver = false; } if (deliver) { // Check if the activity should be 'skipped'. String result = null; ISeqRuleset skippedRules = ioFrom.at.getPreSeqRules(); if (skippedRules != null) { result = skippedRules.evaluate(SeqRuleset.RULE_TYPE_SKIPPED, ioFrom.at, mRetry); } // If the rule evaluation did not return null, the activity is // skipped if (result != null) { Walk walk = walkTree(iDirection, iPrevDirection, false, ioFrom.at, true); if (walk.at == null) { deliver = false; } else { if (_Debug) { System.out.println(" ::--> RECURSION <--::"); } ioFrom.at = walk.at; // Test if we've switched directions... if (iPrevDirection == ADLSequencer.FLOW_BACKWARD && walk.direction == ADLSequencer.FLOW_BACKWARD) return walkActivity(ADLSequencer.FLOW_BACKWARD, ADLSequencer.FLOW_NONE, ioFrom); else return walkActivity(iDirection, iPrevDirection, ioFrom); } } else { // The activity was not skipped, make sure it is enabled if (!checkActivity(ioFrom.at)) { // Make sure the activity being considered is a leaf if (ioFrom.at.hasChildren(false)) { Walk walk = walkTree(iDirection, ADLSequencer.FLOW_NONE, true, ioFrom.at, true); if (walk.at != null) { ioFrom.at = walk.at; if (iDirection == ADLSequencer.FLOW_BACKWARD && walk.direction == ADLSequencer.FLOW_FORWARD) { if (_Debug) { System.out.println(" ::--> REVERSING"); } deliver = walkActivity(ADLSequencer.FLOW_FORWARD, ADLSequencer.FLOW_BACKWARD, ioFrom); } else { deliver = walkActivity(iDirection, ADLSequencer.FLOW_NONE, ioFrom); } } else { deliver = false; } } else { if (_Debug) { System.out.println(" ::--> Found a leaf"); } } } else { deliver = false; } } } if (_Debug) { System.out.println(" ::--> " + deliver); if (ioFrom.at != null) { System.out.println(" ::--> " + ioFrom.at.getID()); } else { System.out.println(" ::--> NULL"); } System.out.println(" :: ADLSequencer --> END - " + "walkActivity"); } return deliver; } /** * Traverses the activity tree as defined by the Flow Tree Traversal * Subprocess. * * @param iDirection * Indicates the direction the tree is to be traversed. * * @param iPrevDirection * Indicates the previous traversal direction. * * @param iEnter * Indicates if the children of the acivity should b considered. * * @param iFrom * Indicates where the traversal should start at. * * @param iControl * Indicates if control modes should be enforced during this * traversal. * * @return Indicates the 'next' activity in the traversal direction, or * <code>null</code> if no 'next' activity can be identified. */ private Walk walkTree(int iDirection, int iPrevDirection, boolean iEnter, SeqActivity iFrom, boolean iControl) { // This method implements Flow Subprocess SB.2.1 if (_Debug) { System.out.println(" :: ADLSequencer --> BEGIN - " + "walkTree"); System.out.println( " Dir ::--> " + (iDirection == ADLSequencer.FLOW_BACKWARD ? "Backward" : "Forward")); if (iPrevDirection == ADLSequencer.FLOW_NONE) { System.out.println(" Prev ::--> None"); } else { System.out.println( " Prev ::--> " + (iPrevDirection == ADLSequencer.FLOW_BACKWARD ? "Backward" : "Forward")); } System.out.println(" ::--> " + (iEnter ? "Enter" : "Don't Enter")); System.out.println(" ::--> Control? " + (iControl ? "Yes" : "No")); if (iFrom != null) { System.out.println(" ::--> " + iFrom.getID()); } else { System.out.println(" ::--> NULL"); } } SeqActivity next = null; SeqActivity parent = null; int direction = iDirection; boolean reversed = false; boolean done = false; boolean endSession = false; if (iFrom == null) { if (_Debug) { System.out.println(" ::--> Walked off the Activity Tree"); } // The sequencing session is over endSession = true; done = true; } else { parent = iFrom.getParent(); } // Test if we have skipped all of the children in a 'forward-only' // cluster traversing backward if (!done && parent != null) { if (iPrevDirection == ADLSequencer.FLOW_BACKWARD) { if (iFrom.getNextSibling(false) == null) { // Switch traversal direction direction = ADLSequencer.FLOW_BACKWARD; // Move our starting point iFrom = (parent.getChildren(false).get(0)); reversed = true; if (_Debug) { System.out.println(" :: REVERSING DIRECTION :: " + iFrom.getID()); } } } } if (!done && direction == ADLSequencer.FLOW_FORWARD) { if (iFrom.getID().equals(mSeqTree.getLastLeaf())) { // We are at the last leaf of the tree, the sequencing // session is over done = true; endSession = true; if (_Debug) { System.out.println(" ::--> Last Leaf"); } } if (!done) { // Is the activity a leaf or a cluster that should not be // entered if (!iFrom.hasChildren(false) || !iEnter) { // String result = null; // // if ( iControl ) // { // // if ( _Debug ) // { // System.out.println(" ::--> TESTING FORWARD BLOCK"); // } // // // Attempt to get rule information from the activity node // SeqRuleset stopTrav = iFrom.getPreSeqRules(); // // if ( stopTrav != null ) // { // result = // stopTrav.evaluate(SeqRuleset.RULE_TYPE_FORWARDBLOCK, // iFrom); // } // } // // // If the rule evaluation did not returns null, move to // // the activity's sibling // if ( result == null ) // { next = iFrom.getNextSibling(false); if (next == null) { if (_Debug) { System.out.println(" ::--> FORWARD RECURSION <--::"); } Walk walk = walkTree(direction, ADLSequencer.FLOW_NONE, false, parent, iControl); next = walk.at; endSession = walk.endSession; } } // else // { // if ( _Debug ) // { // System.out.println(" ::--> BLOCKED <--::"); // } // // done = true; // } // } // Enter the Cluster else { // Return the first child activity next = (iFrom.getChildren(false).get(0)); } } } else if (!done && direction == ADLSequencer.FLOW_BACKWARD) { // Can't walk off the root of the tree if (parent != null) { // Is the activity a leaf or a cluster that should not be // entered if (!iFrom.hasChildren(false) || !iEnter) { // Make sure we can move backward if (iControl && !reversed) { if (parent.getControlForwardOnly()) { if (_Debug) { System.out.println(" ::--> Forward Only " + "Control Mode " + "violation at --> " + iFrom.getID()); System.out.println(" :: ADLSequencer --> END - " + "walkTree"); } done = true; } } if (!done) { next = iFrom.getPrevSibling(false); if (next == null) { if (_Debug) { System.out.println(" ::--> BACKWARD RECURSION " + "<--::"); } Walk walk = walkTree(direction, ADLSequencer.FLOW_NONE, false, parent, iControl); next = walk.at; endSession = walk.endSession; } } } // Enter the cluster backward else { if (iFrom.getControlForwardOnly()) { // Return the first child activity next = (iFrom.getChildren(false).get(0)); // And switch direction direction = ADLSequencer.FLOW_FORWARD; } else { int size = iFrom.getChildren(false).size(); // Return the last child activity next = (iFrom.getChildren(false).get(size - 1)); } } } else { if (_Debug) { System.out.println(" ::--> ERROR : Walked off the root"); } } } else if (!done) { if (_Debug) { System.out.println(" ::--> ERROR : Invalid direction"); } } if (_Debug) { if (next != null) { System.out.println(" ::--> " + next.getID()); } else { System.out.println(" ::--> NULL"); } System.out.println(" ::--> End Session? " + ((endSession) ? "YES" : "NO")); System.out.println( " ::--> MOVING ---> " + ((direction == ADLSequencer.FLOW_FORWARD) ? "Forward" : "Backward")); System.out.println(" :: ADLSequencer --> END - " + "walkTree"); } Walk walk = new Walk(); walk.at = next; walk.direction = direction; walk.endSession = endSession; return walk; } } // end ADLSequencer