/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * CogTool is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool.controller; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import edu.cmu.cs.hcii.cogtool.model.AUndertaking; import edu.cmu.cs.hcii.cogtool.model.CognitiveModelGenerator; import edu.cmu.cs.hcii.cogtool.model.Design; import edu.cmu.cs.hcii.cogtool.model.Frame; import edu.cmu.cs.hcii.cogtool.model.ActionScriptStep; import edu.cmu.cs.hcii.cogtool.model.Demonstration; import edu.cmu.cs.hcii.cogtool.model.DemonstrationState; import edu.cmu.cs.hcii.cogtool.model.Script; import edu.cmu.cs.hcii.cogtool.model.AScriptStep; import edu.cmu.cs.hcii.cogtool.model.TaskApplication; import edu.cmu.cs.hcii.cogtool.model.Transition; import edu.cmu.cs.hcii.cogtool.model.IWidget; import edu.cmu.cs.hcii.cogtool.model.SimpleWidgetGroup; import edu.cmu.cs.hcii.cogtool.model.Project; import edu.cmu.cs.hcii.cogtool.util.AUndoableEdit; import edu.cmu.cs.hcii.cogtool.util.ArrayIterable; import edu.cmu.cs.hcii.cogtool.util.IUndoableEdit; import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifier; public class DemoStateManager { public static final Boolean INVALIDATING = Boolean.TRUE; public static final Boolean OBSOLETING = Boolean.FALSE; public static final Boolean BENIGN = null; /** * Use IDesignUndoableEdit during design editing (DesignEditorController * and FrameEditorController) when the edit can affect what steps * the ICognitiveModelGenerator might generate from a Demonstration's steps * (i.e., ObsoletingEdit) or when the edit may delete (either during * do/redo or during undo) a design component that a Demonstration or * any of its steps uses (i.e., InvalidatingEdit). */ public interface IDesignUndoableEdit extends IUndoableEdit { public Boolean getEditNature(); } public static class DesignUndoableEdit extends AUndoableEdit implements IDesignUndoableEdit { protected DemoStateManager stateMgr; public DesignUndoableEdit(ListenerIdentifier listenerID, DemoStateManager mgr) { super(listenerID); stateMgr = mgr; } @Override public void die() { super.die(); stateMgr.loseEdit(this); } public Boolean getEditNature() { // By default, edits are not invalidating or obsoleting. // These edits may have an effect on non-editable demonstrations. return BENIGN; } } public static class ObsoletingEdit extends DesignUndoableEdit { public ObsoletingEdit(ListenerIdentifier listenerID, DemoStateManager mgr) { super(listenerID, mgr); } @Override public Boolean getEditNature() { return OBSOLETING; } } public static class InvalidatingEdit extends DesignUndoableEdit { public InvalidatingEdit(ListenerIdentifier listenerID, DemoStateManager mgr) { super(listenerID, mgr); } @Override public Boolean getEditNature() { return INVALIDATING; } } /** * Use IDemoUndoableEdit during demonstration editing (SEDemoController) * when the edit changes the set of demonstrated steps, either by * insertion or deletion. */ public interface IDemoUndoableEdit extends IUndoableEdit { /** * Return currently "unused" AScriptStep's for the undoable edit * corresponding to a modification during demonstration. The steps * should be enumerated in order when using the Set's iterator * (see the constructor for ADemoUndoableEdit). */ public Set<AScriptStep> getDemoSteps(); } public interface IDemoStateTracking extends Map<IDesignUndoableEdit, DemonstrationState> { // Essentially, an alias. } public static class DemoStateTracking extends HashMap<IDesignUndoableEdit, DemonstrationState> implements IDemoStateTracking { // Again, an alias } public static abstract class ADemoUndoableEdit extends AUndoableEdit implements IDemoUndoableEdit { // Although we record these as Set objects, their enumeration order // when iterating through them should be guaranteed (see the comment // for the constructor below). protected Set<AScriptStep> redoDemoSteps; protected Set<AScriptStep> undoDemoSteps; protected Demonstration demo; protected DemoStateManager mgr; /** * Need to "flip" the noted edits when doing an undo or redo. * That is, design edits that affected "used" steps must be moved * to the map associated with this IDemoUndoableEdit in the mgr's * editedStepsTracking. Similarly, design edits that affected * "unused" steps must be moved to the map associated with the steps' * Demonstration in the mgr's demoStateTracking. Note that, since * all "unused" steps become "used" steps during this undo/redo, * we only need to test for affected "used" steps; we can simply move * all noted edits for "unused" steps. */ protected void flipNotedEdits() { IDemoStateTracking editsAffectingUsedStates = mgr.demoStateTracking.get(demo); IDemoStateTracking editsAffectingUnusedSteps = mgr.editedStepsTracking.get(this); // Recursively enumerate each IDesignUndoableEdit -> AScriptStep, // using the call stack to hold them temporarily as they are // deleted from editsAffectingUnusedSteps before the applicable // entries from editsAffectingUsedStates are moved into it. Iterator<Map.Entry<IDesignUndoableEdit, DemonstrationState>> editsUnused = editsAffectingUnusedSteps.entrySet().iterator(); flipNotedEditsUnused(editsAffectingUsedStates, editsAffectingUnusedSteps, editsUnused); } /** * Recursive step; all noted edits for "unused" steps may be moved * into the editsAffectingUsedStates. If there is another "unused" * step from editsUnused (iterating over editsAffectingUnusedSteps), * first remove its entry from editsAffectingUnusedSteps, then recur, * then add to editsAffectingUsedStates. This way, less work needs to * be done in flipNotedEditsUsed to move only the edits necessary. */ protected void flipNotedEditsUnused(IDemoStateTracking editsAffectingUsedStates, IDemoStateTracking editsAffectingUnusedSteps, Iterator<Map.Entry<IDesignUndoableEdit, DemonstrationState>> editsUnused) { if (editsUnused.hasNext()) { Map.Entry<IDesignUndoableEdit, DemonstrationState> editUnused = editsUnused.next(); // Always remove before the recursive step editsUnused.remove(); flipNotedEditsUnused(editsAffectingUsedStates, editsAffectingUnusedSteps, editsUnused); // Put IDesignUndoableEdit -> AScriptStep into "used" map editsAffectingUsedStates.put(editUnused.getKey(), editUnused.getValue()); } else { // Since we removed the entry before the recursive step, // editsAffectingUnusedSteps should be empty! flipNotedEditsUsed(editsAffectingUsedStates, editsAffectingUnusedSteps); // After this, the return of each recursive step will // now add the "unused" edit entries into the "used" edit map. } } /** * At this point, editsAffectingUnusedSteps should be empty! */ protected void flipNotedEditsUsed(IDemoStateTracking editsAffectingUsedStates, IDemoStateTracking editsAffectingUnusedSteps) { // The set of currently "used" steps that have just become "unused" // The right set of steps (i.e., those that will now be "unused") // will be returned because the super.undo/redo call has already // toggled the sense of "hasBeenDone". Edits will be moved from // demoStateTracking to editedStepsTracking. Set<AScriptStep> checkSteps = getDemoSteps(); Iterator<Map.Entry<IDesignUndoableEdit, DemonstrationState>> editsUsed = editsAffectingUsedStates.entrySet().iterator(); while (editsUsed.hasNext()) { Map.Entry<IDesignUndoableEdit, DemonstrationState> editUsed = editsUsed.next(); DemonstrationState demoState = editUsed.getValue(); // If the demoState is a step and if it has just become // "unused", remove the edit and put into the other map. if (checkSteps.contains(demoState)) { // Remove from editsAffectingUsedStates editsUsed.remove(); // Enter IDesignUndoableEdit -> AScriptStep into "unused" editsAffectingUnusedSteps.put(editUsed.getKey(), demoState); } } } /** * It is expected that the redoStates/undoStates enumerate the * contained AScriptStep instances in the SAME ORDER as the steps * were when they were in the Demonstration. * This means the given Set instances should either be * (a) empty, (b) singleton (see Collections.singleton(o)), or * (c) an instance of LinkedHashSet (possibly TreeSet, but "eh!") */ public ADemoUndoableEdit(ListenerIdentifier listenerID, Demonstration affectedDemo, Set<AScriptStep> redoSteps, Set<AScriptStep> undoSteps, DemoStateManager demoUndoMgr) { super(listenerID); demo = affectedDemo; redoDemoSteps = redoSteps; undoDemoSteps = undoSteps; mgr = demoUndoMgr; flipNotedEditsUsed(mgr.demoStateTracking.get(demo), mgr.trackEdits(this)); } @Override public void redo() { super.redo(); flipNotedEdits(); } @Override public void undo() { super.undo(); flipNotedEdits(); } @Override public void die() { super.die(); mgr.stopTrackingEdits(this); } public Set<AScriptStep> getDemoSteps() { return isDone() ? undoDemoSteps : redoDemoSteps; } } // Maps Design to DemoStateManager instances protected static Map<Design, DemoStateManager> stateMgrRegistry = new HashMap<Design, DemoStateManager>(); public static DemoStateManager getStateManager(Project project, Design design) { DemoStateManager mgr = stateMgrRegistry.get(design); if (mgr == null) { mgr = new DemoStateManager(project, design); stateMgrRegistry.put(design, mgr); Iterator<TaskApplication> designTAs = project.taskApplicationsForDesign(design).values().iterator(); while (designTAs.hasNext()) { TaskApplication ta = designTAs.next(); mgr.trackEdits(ta.getDemonstration()); } } return mgr; } public static void removeStateManager(Project project, Design design) { DemoStateManager mgr = stateMgrRegistry.remove(design); if (mgr != null) { Iterator<TaskApplication> designTAs = project.taskApplicationsForDesign(design).values().iterator(); while (designTAs.hasNext()) { TaskApplication ta = designTAs.next(); mgr.stopTrackingEdits(ta.getDemonstration()); } } } public static void stopTrackingEdits(Project project, TaskApplication taskApp) { DemoStateManager stateMgr = getStateManager(project, taskApp.getDesign()); if (stateMgr != null) { stateMgr.stopTrackingEdits(taskApp.getDemonstration()); } } // The manager's scope protected Project project; protected Design design; // Maps Demonstration to map between edit token and IDemonstrationState; // this tracks the (first) AScriptStep instance affected by each edit. protected Map<Demonstration, IDemoStateTracking> demoStateTracking = new HashMap<Demonstration, IDemoStateTracking>(); // Maps IDemoUndoableEdit to map between edit token and AScriptStep; // this tracks the (first) AScriptStep instance affected by each edit. protected Map<IDemoUndoableEdit, IDemoStateTracking> editedStepsTracking = new HashMap<IDemoUndoableEdit, IDemoStateTracking>(); public DemoStateManager(Project p, Design d) { project = p; design = d; } public Project getProject() { return project; } public Design getDesign() { return design; } public void trackEdits(Demonstration forDemo) { demoStateTracking.put(forDemo, new DemoStateTracking()); } public void stopTrackingEdits(Demonstration forDemo) { demoStateTracking.remove(forDemo); } protected IDemoStateTracking trackEdits(IDemoUndoableEdit forEdit) { IDemoStateTracking newEditsAffectingUnusedSteps = new DemoStateTracking(); editedStepsTracking.put(forEdit, newEditsAffectingUnusedSteps); return newEditsAffectingUnusedSteps; } protected void stopTrackingEdits(IDemoUndoableEdit forEdit) { editedStepsTracking.remove(forEdit); } protected static abstract class EditValidator { /** * Subclasses that know the specific edit object (and its type) * should override this to determine if the given demonstration uses * the object; if so, the edit should be noted (using invalidating) * and the "earliest" using component of the demonstration returned. */ protected abstract DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating); /** * For each Demonstration, toggle the edit (note vs. revert) if * it uses the associated edit object. */ protected void validateDemo(Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, IDesignUndoableEdit edit) { Boolean nature = edit.getEditNature(); // demoEntries holds the entries from the demoStateTracking map, // which maps each Demonstration to the associated map between an // IDesignUndoableEdit and the earliest IDemonstrationState that // uses the object edited by the edit. while (demoEntries.hasNext()) { Map.Entry<Demonstration, IDemoStateTracking> entry = demoEntries.next(); Demonstration demo = entry.getKey(); IDemoStateTracking editToState = entry.getValue(); // Determine if the associated map holds an entry for the // given edit; if so, revert the edit. Otherwise, we must // determine (possibly again) whether some IDemonstrationState // that is part of the Demonstration uses the edit object. DemonstrationState demoState = editToState.remove(edit); boolean invalidating; if (! demo.isEditable()) { invalidating = true; } else if (nature == BENIGN) { continue; } else { invalidating = nature.booleanValue(); } if (demoState != null) { demo.revertEdit(demoState, invalidating); } else { demoState = noteEditDemo(demo, invalidating); // If the edit object is used (and noted), record in the map if (demoState != null) { editToState.put(edit, demoState); } } } } /** * Subclasses that know the specific edit object (and its type) * should override this to determine if the given IDemoUndoableEdit uses * the object; if so, the edit should be noted (using invalidating) * and the "earliest" using component of the demonstration returned. */ protected abstract AScriptStep noteEdit(IDemoUndoableEdit demoEdit, boolean invalidating); /** * For each IDemoUndoableEdit, toggle the edit (note vs. revert) if * one of its steps uses the associated edit object. */ protected void validateEditedSteps(Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { Boolean nature = edit.getEditNature(); if (nature == BENIGN) { return; } boolean invalidating = nature.booleanValue(); // editStepEntries holds the entries from the editedStatesTracking // map, which maps each IDemoUndoableEdit to the associated map // between an IDesignUndoableEdit and the earliest AScriptStep that // uses the object edited by the edit. An IDemoUndoableEdit // keeps track of the currently "unused" sequence of AScriptStep // instances that were replaced by an edit to an Demonstration. while (editStepEntries.hasNext()) { Map.Entry<IDemoUndoableEdit, IDemoStateTracking> entry = editStepEntries.next(); IDemoStateTracking editToDemoState = entry.getValue(); // Determine if the associated map holds an entry for the // given edit; if so, revert the edit. Otherwise, we must // determine (possibly again) whether some AScriptStep // that is part of the IDemoUndoableEdit uses the edit object. DemonstrationState step = editToDemoState.remove(edit); if (step != null) { step.revertEdit(step, invalidating); } else { step = noteEdit(entry.getKey(), invalidating); if (step != null) { editToDemoState.put(edit, step); } } } } protected void observeEdit(Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> stepEditEntries, IDesignUndoableEdit edit) { validateDemo(demoEntries, edit); validateEditedSteps(stepEditEntries, edit); } } /** * Represents a design edit that modifies a single object. */ protected static abstract class SingleEditValidator extends EditValidator { /** * Subclasses should override to share the implementation to determine * whether the associated modified design object is used by the given * IDemonstrationState (which is either the Demonstration or one * step from the currently "unused" sequence of IScriptSteps held * by an IDemoUndoableEdit). */ protected abstract DemonstrationState noteEdit(DemonstrationState s, boolean invalidating); @Override protected DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating) { return noteEdit(demo, invalidating); } @Override protected AScriptStep noteEdit(IDemoUndoableEdit demoEdit, boolean invalidating) { Iterator<AScriptStep> demoSteps = demoEdit.getDemoSteps().iterator(); // Check each AScriptStep that are currently "unused" while (demoSteps.hasNext()) { AScriptStep step = demoSteps.next(); // If noteEdit returns non-null, it will be step! if (noteEdit(step, invalidating) != null) { return step; } } return null; } } /** * Represents a design edit that modifies (possibly) multiple objects. */ protected static abstract class MultipleEditValidator<T> extends EditValidator { /** * This must be initialized for each use by a call to reset() */ protected Iterable<? extends T> editedObjects; protected void reset(Iterable<? extends T> objects) { this.editedObjects = objects; } /** * Subclasses should override to share the implementation to determine * whether any of the associated modified design objects is used by the * given AScriptStep, which is one of the currently "unused" steps held * by an IDemoUndoableEdit. */ protected abstract boolean usesEditedObject(AScriptStep step, T object); @Override protected AScriptStep noteEdit(IDemoUndoableEdit demoEdit, boolean invalidating) { Iterator<AScriptStep> steps = demoEdit.getDemoSteps().iterator(); while (steps.hasNext()) { AScriptStep step = steps.next(); // Check each edit object if this step uses it; // return first such step. Iterator<? extends T> checkObjects = this.editedObjects.iterator(); while (checkObjects.hasNext()) { if (usesEditedObject(step, checkObjects.next())) { step.noteEdit(invalidating); return step; } } } return null; } } protected static class FrameEditValidator extends SingleEditValidator { protected Frame frame; @Override protected DemonstrationState noteEdit(DemonstrationState s, boolean invalidating) { return s.noteFrameEdit(frame, invalidating); } public void noteFrameEdit(Frame f, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { frame = f; observeEdit(demoEntries, editStepEntries, edit); } } protected static class FramesEditValidator extends MultipleEditValidator<Frame> { protected Frame[] frames; @Override protected DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating) { return demo.noteFramesEdit(frames, invalidating); } @Override protected boolean usesEditedObject(AScriptStep step, Frame object) { return step.usesFrame(object); } public void noteFramesEdit(Frame[] frameSet, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { frames = frameSet; reset(new ArrayIterable<Frame>(frames)); observeEdit(demoEntries, editStepEntries, edit); } } protected static class WidgetEditValidator extends SingleEditValidator { protected IWidget widget; @Override protected DemonstrationState noteEdit(DemonstrationState s, boolean invalidating) { return s.noteWidgetEdit(widget, invalidating); } public void noteWidgetEdit(IWidget w, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { widget = w; observeEdit(demoEntries, editStepEntries, edit); } } protected static class WidgetsEditValidator extends MultipleEditValidator<IWidget> { protected Iterable<? extends IWidget> widgets; @Override protected DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating) { return demo.noteWidgetsEdit(widgets.iterator(), invalidating); } @Override protected boolean usesEditedObject(AScriptStep step, IWidget object) { return step.usesWidget(object); } public void noteWidgetsEdit(Iterable<? extends IWidget> widgetSet, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { widgets = widgetSet; reset(widgets); observeEdit(demoEntries, editStepEntries, edit); } } protected static class WidgetGroupEditValidator extends MultipleEditValidator<IWidget> { protected SimpleWidgetGroup widgetGroup; @Override protected DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating) { return demo.noteWidgetsEdit(widgetGroup.iterator(), invalidating); } @Override protected boolean usesEditedObject(AScriptStep step, IWidget object) { return step.usesWidget(object); } public void noteWidgetsEdit(SimpleWidgetGroup widgetSet, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { widgetGroup = widgetSet; reset(widgetGroup); observeEdit(demoEntries, editStepEntries, edit); } } protected static class ReorderEditValidator extends WidgetGroupEditValidator { protected SimpleWidgetGroup otherGroup; protected class TwoGroupIterator implements Iterator<IWidget> { protected boolean inFirstGroup = true; protected Iterator<IWidget> groupIterator = null; public TwoGroupIterator() { groupIterator = widgetGroup.iterator(); } public boolean hasNext() { if (groupIterator != null) { if (groupIterator.hasNext()) { return true; } if (inFirstGroup) { inFirstGroup = false; groupIterator = otherGroup.iterator(); if (groupIterator.hasNext()) { return true; } } groupIterator = null; } return false; } public IWidget next() { if (hasNext()) { return groupIterator.next(); } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } protected class TwoGroupIterable implements Iterable<IWidget> { public Iterator<IWidget> iterator() { return new TwoGroupIterator(); } } @Override protected DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating) { return demo.noteWidgetsEdit(new TwoGroupIterator(), invalidating); } public void noteWidgetsEdit(SimpleWidgetGroup widgetSet1, SimpleWidgetGroup widgetSet2, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { widgetGroup = widgetSet1; otherGroup = widgetSet2; reset(new TwoGroupIterable()); observeEdit(demoEntries, editStepEntries, edit); } } protected static class TransitionEditValidator extends SingleEditValidator { protected Transition transition; @Override protected DemonstrationState noteEdit(DemonstrationState s, boolean invalidating) { // If the state is an ActionScriptStep, then this edit // *has* to be INVALIDATING since some part of the transition's // definition has changed; if the ActionScriptStep uses the // transition, then it shares that definition with the transition // and, therefore, it no longer shares that definition. boolean reallyInvalidating = invalidating || (s instanceof ActionScriptStep); return s.noteTransitionEdit(transition, reallyInvalidating); } public void noteTransitionEdit(Transition t, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { transition = t; observeEdit(demoEntries, editStepEntries, edit); } } protected static class TransitionsEditValidator extends MultipleEditValidator<Transition> { protected Transition[] transitions; @Override protected DemonstrationState noteEditDemo(Demonstration demo, boolean invalidating) { return demo.noteTransitionsEdit(transitions, invalidating); } @Override protected boolean usesEditedObject(AScriptStep step, Transition t) { return step.usesTransition(t); } public void noteTransitionsEdit(Transition[] transitionSet, Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries, Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries, IDesignUndoableEdit edit) { transitions = transitionSet; reset(new ArrayIterable<Transition>(transitions)); observeEdit(demoEntries, editStepEntries, edit); } } protected static FrameEditValidator frameEditValidator = new FrameEditValidator(); protected static FramesEditValidator framesEditValidator = new FramesEditValidator(); protected static WidgetEditValidator widgetEditValidator = new WidgetEditValidator(); protected static WidgetsEditValidator widgetsEditValidator = new WidgetsEditValidator(); protected static WidgetGroupEditValidator widgetGroupEditValidator = new WidgetGroupEditValidator(); protected static ReorderEditValidator reorderEditValidator = new ReorderEditValidator(); protected static TransitionEditValidator transitionEditValidator = new TransitionEditValidator(); protected static TransitionsEditValidator transitionsEditValidator = new TransitionsEditValidator(); public void noteFrameEdit(Frame frame, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); frameEditValidator.noteFrameEdit(frame, demoEntries, editStepEntries, edit); } public void noteFramesEdit(Frame[] frames, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); framesEditValidator.noteFramesEdit(frames, demoEntries, editStepEntries, edit); } public void noteWidgetEdit(IWidget widget, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); widgetEditValidator.noteWidgetEdit(widget, demoEntries, editStepEntries, edit); } public void noteWidgetsEdit(Iterable<? extends IWidget> widgets, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); widgetsEditValidator.noteWidgetsEdit(widgets, demoEntries, editStepEntries, edit); } public void noteGroupEdit(SimpleWidgetGroup group, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); widgetGroupEditValidator.noteWidgetsEdit(group, demoEntries, editStepEntries, edit); } public void noteReorderEdit(SimpleWidgetGroup group1, SimpleWidgetGroup group2, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); if (group1 == null) { widgetGroupEditValidator.noteWidgetsEdit(group2, demoEntries, editStepEntries, edit); } else if ((group1 != group2) && (group2 != null)) { reorderEditValidator.noteWidgetsEdit(group1, group2, demoEntries, editStepEntries, edit); } else { widgetGroupEditValidator.noteWidgetsEdit(group1, demoEntries, editStepEntries, edit); } } public void noteTransitionEdit(Transition transition, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); transitionEditValidator.noteTransitionEdit(transition, demoEntries, editStepEntries, edit); } public void noteTransitionsEdit(Transition[] transitions, IDesignUndoableEdit edit) { Iterator<Map.Entry<Demonstration, IDemoStateTracking>> demoEntries = demoStateTracking.entrySet().iterator(); Iterator<Map.Entry<IDemoUndoableEdit, IDemoStateTracking>> editStepEntries = editedStepsTracking.entrySet().iterator(); transitionsEditValidator.noteTransitionsEdit(transitions, demoEntries, editStepEntries, edit); } protected void loseEdit(IDesignUndoableEdit edit, Iterator<IDemoStateTracking> entries) { while (entries.hasNext()) { IDemoStateTracking editToState = entries.next(); editToState.remove(edit); } } public void loseEdit(IDesignUndoableEdit edit) { loseEdit(edit, demoStateTracking.values().iterator()); loseEdit(edit, editedStepsTracking.values().iterator()); } public interface IConformanceUndoRedo { public void redo(); public void undo(); } protected class ConformanceUndoRedo implements IConformanceUndoRedo { protected IDemoStateTracking obsoletingEdits = new DemoStateTracking(); protected Set<DemonstrationState> obsoleteStates = new HashSet<DemonstrationState>(); protected Demonstration demo; protected IDemoStateTracking editToState; public ConformanceUndoRedo(Demonstration d) { demo = d; editToState = demoStateTracking.get(demo); if (! demo.isEditable()) { return; } if (editToState == null) { throw new IllegalStateException("Missing demo in DemoStateManager"); } Iterator<Map.Entry<IDesignUndoableEdit, DemonstrationState>> demoEdits = editToState.entrySet().iterator(); while (demoEdits.hasNext()) { Map.Entry<IDesignUndoableEdit, DemonstrationState> entry = demoEdits.next(); IDesignUndoableEdit edit = entry.getKey(); Boolean nature = edit.getEditNature(); if (nature == OBSOLETING) { DemonstrationState demoOrStep = entry.getValue(); demo.revertEdit(demoOrStep, DemonstrationState.OBSOLETING); obsoletingEdits.put(edit, demoOrStep); demoEdits.remove(); } } demo.restoreConformance(obsoleteStates); } /** * Undo/redo support for managing obsoleting edits when conformance * has been restored. * Undo support for regenerated scripts: inverse of restoreConformance. */ protected void undoRedoConformance() { Iterator<Map.Entry<IDesignUndoableEdit, DemonstrationState>> edits = obsoletingEdits.entrySet().iterator(); while (edits.hasNext()) { Map.Entry<IDesignUndoableEdit, DemonstrationState> entry = edits.next(); IDesignUndoableEdit edit = entry.getKey(); DemonstrationState demoOrStep = editToState.remove(edit); if (demoOrStep != null) { demo.revertEdit(demoOrStep, DemonstrationState.OBSOLETING); } else { demoOrStep = entry.getValue(); demo.restoreEdit(demoOrStep, DemonstrationState.OBSOLETING); // key is IDesignUndoableEdit editToState.put(entry.getKey(), demoOrStep); } } } public void redo() { undoRedoConformance(); Iterator<DemonstrationState> savedObsoletes = obsoleteStates.iterator(); while (savedObsoletes.hasNext()) { DemonstrationState demoState = savedObsoletes.next(); demoState.revertEdit(demoState, DemonstrationState.OBSOLETING); } } public void undo() { undoRedoConformance(); Iterator<DemonstrationState> savedObsoletes = obsoleteStates.iterator(); while (savedObsoletes.hasNext()) { DemonstrationState demoState = savedObsoletes.next(); demoState.noteEdit(DemonstrationState.OBSOLETING); } } } /** * Revert obsoleting edits, inserting those edits (and the associated * IDemonstrationState instances) in the given map. */ public IConformanceUndoRedo restoreConformance(Demonstration demo) { return new ConformanceUndoRedo(demo); } public static Script ensureScript(TaskApplication ta, CognitiveModelGenerator gen) { if (gen != null) { Script script = ta.getScript(gen); if (script == null) { script = new Script(ta.getDemonstration(), gen); ta.setScript(gen, script); } return script; } return null; } public static TaskApplication ensureTaskApplication(Project project, AUndertaking task, Design design, CognitiveModelGenerator gen, DemoStateManager demoMgr) { TaskApplication ta = project.getTaskApplication(task, design); if (ta == null) { ta = new TaskApplication(task, design); project.setTaskApplication(ta); demoMgr.trackEdits(ta.getDemonstration()); } // If no script exists for this cell, create one // However, if the given model generator (gen) is null, then don't // create a script. ensureScript(ta, gen); return ta; } }