/**
   * Specialized stack-like structure that allows for multiple stacks based on a key. Each key
   * passed into the <code>KeyedStack</code> is given a unique <code>Stack</code> and all
   * interactions with the <code>KeyedStack</code> require said key.
   *
   * @author Michael Pogran
   */
  private class KeyedStack<K, V> {
    private HashMap<K, java.util.Stack<V>> stackMap = Maps.newHashMap();

    public V push(V value, K key) {
      java.util.Stack<V> stack = this.stackMap.get(key);
      if (stack == null) {
        stack = new java.util.Stack<V>();
        this.stackMap.put(key, stack);
      }
      return stack.push(value);
    }

    public V peek(K key) {
      java.util.Stack<V> stack = this.stackMap.get(key);
      if (stack != null) {
        return stack.peek();
      } else {
        return null;
      }
    }

    public V pop(K key) {
      java.util.Stack<V> stack = this.stackMap.get(key);
      if (stack != null) {
        return stack.pop();
      } else {
        return null;
      }
    }

    public void clear() {
      stackMap = edu.cmu.cs.dennisc.java.util.Maps.newHashMap();
    }
  }
/** @author Dennis Cosgrove */
public class PlusMinusOperation extends NumberPadOperation {

  private static java.util.Map<NumberModel<?>, PlusMinusOperation> map =
      edu.cmu.cs.dennisc.java.util.Maps.newHashMap();

  public static synchronized PlusMinusOperation getInstance(NumberModel<?> model) {
    PlusMinusOperation rv = map.get(model);
    if (rv != null) {
      // pass
    } else {
      rv = new PlusMinusOperation(model);
      map.put(model, rv);
    }
    return rv;
  }

  private PlusMinusOperation(NumberModel<?> model) {
    super(java.util.UUID.fromString("6845e168-dfce-4f9e-b94f-d5674613f38c"), model);
    this.setName("\u00B1");
  }

  @Override
  protected void perform(org.lgna.croquet.history.CompletionStep<?> step) {
    this.numberModel.negate();
    step.finish();
  }
}
/** @author Dennis Cosgrove */
public class IntegerArithmeticExpressionRightOperandOnlyFillIn
    extends org.alice.ide.croquet.models.cascade.arithmetic
        .ArithmeticExpressionRightOperandOnlyFillIn {
  private static java.util.Map<
          org.lgna.project.ast.ArithmeticInfixExpression.Operator,
          IntegerArithmeticExpressionRightOperandOnlyFillIn>
      map = edu.cmu.cs.dennisc.java.util.Maps.newHashMap();

  public static IntegerArithmeticExpressionRightOperandOnlyFillIn getInstance(
      org.lgna.project.ast.ArithmeticInfixExpression.Operator operator) {
    synchronized (map) {
      IntegerArithmeticExpressionRightOperandOnlyFillIn rv = map.get(operator);
      if (rv != null) {
        // pass
      } else {
        rv = new IntegerArithmeticExpressionRightOperandOnlyFillIn(operator);
        map.put(operator, rv);
      }
      return rv;
    }
  }

  private IntegerArithmeticExpressionRightOperandOnlyFillIn(
      org.lgna.project.ast.ArithmeticInfixExpression.Operator operator) {
    super(
        java.util.UUID.fromString("17a328e0-b763-4375-84f5-0a6a63e94964"),
        Integer.class,
        Integer.class,
        operator,
        Integer.class);
  }
}
/** @author Dennis Cosgrove */
public class ParameterAccessDragModel extends AbstractExpressionDragModel {
  private static edu.cmu.cs.dennisc.java.util.InitializingIfAbsentMap<
          org.lgna.project.ast.UserParameter, ParameterAccessDragModel>
      map = edu.cmu.cs.dennisc.java.util.Maps.newInitializingIfAbsentHashMap();

  public static ParameterAccessDragModel getInstance(org.lgna.project.ast.UserParameter parameter) {
    return map.getInitializingIfAbsent(
        parameter,
        new edu.cmu.cs.dennisc.java.util.InitializingIfAbsentMap.Initializer<
            org.lgna.project.ast.UserParameter, ParameterAccessDragModel>() {
          @Override
          public ParameterAccessDragModel initialize(org.lgna.project.ast.UserParameter parameter) {
            return new ParameterAccessDragModel(parameter);
          }
        });
  }

  private final org.lgna.project.ast.UserParameter parameter;

  private ParameterAccessDragModel(org.lgna.project.ast.UserParameter parameter) {
    super(java.util.UUID.fromString("5b79d910-bbeb-4f9c-9593-28c0697f4036"));
    this.parameter = parameter;
  }

  @Override
  public boolean isPotentialStatementCreator() {
    return false;
  }

  @Override
  protected org.lgna.croquet.Model getDropModel(
      org.alice.ide.ast.draganddrop.BlockStatementIndexPair blockStatementIndexPair) {
    throw new AssertionError();
  }

  @Override
  public org.lgna.project.ast.AbstractType<?, ?, ?> getType() {
    return this.parameter.getValueType();
  }

  @Override
  protected org.lgna.croquet.Model getDropModel(
      org.lgna.project.ast.ExpressionProperty expressionProperty) {
    return org.alice.ide.croquet.models.ast.cascade.expression.ParameterAccessOperation.getInstance(
        this.parameter, expressionProperty);
  }
}
/** @author Dennis Cosgrove */
public class ProjectDocumentFrame extends org.lgna.croquet.PerspectiveDocumentFrame {
  public ProjectDocumentFrame(
      IdeConfiguration ideConfiguration, ApiConfigurationManager apiConfigurationManager) {
    this.apiConfigurationManager = apiConfigurationManager;

    this.noProjectPerspective = new org.alice.ide.perspectives.noproject.NoProjectPerspective(this);
    this.aliceMenuBar = new org.alice.ide.croquet.models.AliceMenuBar(this);
    this.codePerspective = new org.alice.stageide.perspectives.CodePerspective(this, aliceMenuBar);
    this.perspectiveState =
        new org.alice.stageide.perspectives.PerspectiveState(this.codePerspective);
    this.setupScenePerspective =
        new org.alice.stageide.perspectives.SetupScenePerspective(this, aliceMenuBar);

    this.metaDeclarationFauxState = new MetaDeclarationFauxState(this);
    this.instanceFactoryState =
        new org.alice.ide.instancefactory.croquet.InstanceFactoryState(this);
    this.findComposite = new org.alice.ide.croquet.models.project.find.croquet.FindComposite(this);
    this.uploadOperations =
        ideConfiguration != null
            ? ideConfiguration.createUploadOperations(this)
            : new org.lgna.croquet.Operation[0];
    this.iconFactoryManager = apiConfigurationManager.createIconFactoryManager();

    this.perspectiveState.addNewSchoolValueListener(
        new ValueListener<ProjectPerspective>() {
          @Override
          public void valueChanged(ValueEvent<ProjectPerspective> e) {
            ProjectApplication.getActiveInstance().updateTitle();
          }
        });
  }

  private static final javax.swing.KeyStroke CAPTURE_ENTIRE_WINDOW_KEY_STROKE =
      javax.swing.KeyStroke.getKeyStroke(
          java.awt.event.KeyEvent.VK_F12, java.awt.event.InputEvent.SHIFT_MASK);
  private static final javax.swing.KeyStroke CAPTURE_ENTIRE_CONTENT_PANE_KEY_STROKE =
      javax.swing.KeyStroke.getKeyStroke(
          java.awt.event.KeyEvent.VK_F12,
          java.awt.event.InputEvent.SHIFT_MASK | java.awt.event.InputEvent.CTRL_MASK);
  private static final javax.swing.KeyStroke CAPTURE_RECTANGLE_KEY_STROKE =
      javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F12, 0);

  private void registerScreenCaptureKeyStrokes(org.lgna.croquet.views.AbstractWindow<?> window) {
    org.alice.ide.capture.ImageCaptureComposite imageCaptureComposite =
        org.alice.ide.capture.ImageCaptureComposite.getInstance();
    window
        .getContentPane()
        .registerKeyboardAction(
            imageCaptureComposite
                .getCaptureEntireContentPaneOperation()
                .getImp()
                .getSwingModel()
                .getAction(),
            CAPTURE_ENTIRE_CONTENT_PANE_KEY_STROKE,
            org.lgna.croquet.views.SwingComponentView.Condition.WHEN_IN_FOCUSED_WINDOW);
    window
        .getContentPane()
        .registerKeyboardAction(
            imageCaptureComposite
                .getCaptureEntireWindowOperation()
                .getImp()
                .getSwingModel()
                .getAction(),
            CAPTURE_ENTIRE_WINDOW_KEY_STROKE,
            org.lgna.croquet.views.SwingComponentView.Condition.WHEN_IN_FOCUSED_WINDOW);
    if (window == this.getFrame()) {
      // pass
    } else {
      window
          .getContentPane()
          .registerKeyboardAction(
              imageCaptureComposite
                  .getCaptureRectangleOperation()
                  .getImp()
                  .getSwingModel()
                  .getAction(),
              CAPTURE_RECTANGLE_KEY_STROKE,
              org.lgna.croquet.views.SwingComponentView.Condition.WHEN_IN_FOCUSED_WINDOW);
    }
  }

  private void unregisterScreenCaptureKeyStrokes(org.lgna.croquet.views.AbstractWindow<?> window) {
    window.getContentPane().unregisterKeyboardAction(CAPTURE_ENTIRE_WINDOW_KEY_STROKE);
    window.getContentPane().unregisterKeyboardAction(CAPTURE_ENTIRE_CONTENT_PANE_KEY_STROKE);
    window.getContentPane().unregisterKeyboardAction(CAPTURE_RECTANGLE_KEY_STROKE);
  }

  /*package-private*/ void initialize() {
    this.registerScreenCaptureKeyStrokes(this.getFrame());
    this.getInstanceFactoryState().addAndInvokeNewSchoolValueListener(this.instanceFactoryListener);
    org.alice.ide.croquet.models.ui.formatter.FormatterState.getInstance()
        .addNewSchoolValueListener(this.formatterListener);
  }

  @Override
  public void pushWindow(final org.lgna.croquet.views.AbstractWindow<?> window) {
    this.registerScreenCaptureKeyStrokes(window);
    super.pushWindow(window);
  }

  @Override
  public org.lgna.croquet.views.AbstractWindow<?> popWindow() {
    org.lgna.croquet.views.AbstractWindow<?> window = super.popWindow();
    this.unregisterScreenCaptureKeyStrokes(window);
    return window;
  }

  public void disableRendering(
      org.alice.ide.ReasonToDisableSomeAmountOfRendering reasonToDisableSomeAmountOfRendering) {
    this.stack.push(reasonToDisableSomeAmountOfRendering);
    org.alice.stageide.sceneeditor.StorytellingSceneEditor.getInstance()
        .disableRendering(reasonToDisableSomeAmountOfRendering);
  }

  public void enableRendering() {
    if (this.stack.isEmpty()) {
      edu.cmu.cs.dennisc.java.util.logging.Logger.severe(this);
    } else {
      org.alice.ide.ReasonToDisableSomeAmountOfRendering reasonToDisableSomeAmountOfRendering =
          this.stack.pop();
      org.alice.stageide.sceneeditor.StorytellingSceneEditor.getInstance()
          .enableRendering(reasonToDisableSomeAmountOfRendering);
    }
  }

  @Override
  public ProjectDocument getDocument() {
    return org.alice.ide.project.ProjectDocumentState.getInstance().getValue();
  }

  public ApiConfigurationManager getApiConfigurationManager() {
    return this.apiConfigurationManager;
  }

  public org.lgna.croquet.Operation[] getUploadOperations() {
    return this.uploadOperations;
  }

  public org.alice.ide.instancefactory.croquet.InstanceFactoryState getInstanceFactoryState() {
    return this.instanceFactoryState;
  }

  public MetaDeclarationFauxState getMetaDeclarationFauxState() {
    return this.metaDeclarationFauxState;
  }

  public org.alice.ide.perspectives.noproject.NoProjectPerspective getNoProjectPerspective() {
    return this.noProjectPerspective;
  }

  public org.alice.stageide.perspectives.CodePerspective getCodePerspective() {
    return this.codePerspective;
  }

  public org.alice.stageide.perspectives.SetupScenePerspective getSetupScenePerspective() {
    return this.setupScenePerspective;
  }

  public org.lgna.croquet.ItemState<org.alice.ide.perspectives.ProjectPerspective>
      getPerspectiveState() {
    return this.perspectiveState;
  }

  @Override
  public void setPerspective(Perspective perspective) {
    super.setPerspective(perspective);

    // sigh, the listener to perspective state should be enough, but not all perspectives
    // are changed through the state... sigh... then what's the point!?
    ProjectApplication.getActiveInstance().updateTitle();
  }

  public boolean isInCodePerspective() {
    return this.getPerspectiveState().getValue() == this.getCodePerspective();
  }

  public boolean isNoCodePerspective() {
    return (this.getPerspective() instanceof NoProjectPerspective);
  }

  public boolean isInSetupScenePerspective() {
    return this.getPerspectiveState().getValue() == this.getSetupScenePerspective();
  }

  public org.lgna.croquet.Operation getSetToCodePerspectiveOperation() {
    return this.getPerspectiveState().getItemSelectionOperation(this.getCodePerspective());
  }

  public org.lgna.croquet.Operation getSetToSetupScenePerspectiveOperation() {
    return this.getPerspectiveState().getItemSelectionOperation(this.getSetupScenePerspective());
  }

  public void setToCodePerspectiveTransactionlessly() {
    this.getPerspectiveState().setValueTransactionlessly(this.getCodePerspective());
  }

  public void setToSetupScenePerspectiveTransactionlessly() {
    this.getPerspectiveState().setValueTransactionlessly(this.getSetupScenePerspective());
  }

  public org.alice.ide.croquet.models.project.find.croquet.FindComposite getFindComposite() {
    return this.findComposite;
  }

  public org.alice.ide.croquet.models.AliceMenuBar getAliceMenuBar() {
    return this.aliceMenuBar;
  }

  public org.lgna.croquet.meta.MetaState<org.lgna.project.ast.NamedUserType> getTypeMetaState() {
    if (this.typeMetaState != null) {
      // pass
    } else {
      org.alice.ide.declarationseditor.DeclarationTabState declarationTabState =
          this.declarationsEditorComposite.getTabState();
      this.typeMetaState =
          new org.lgna.croquet.meta.StateTrackingMetaState<
              org.lgna.project.ast.NamedUserType, DeclarationComposite<?, ?>>(declarationTabState) {
            @Override
            protected org.lgna.project.ast.NamedUserType getValue(
                org.lgna.croquet.State<org.alice.ide.declarationseditor.DeclarationComposite<?, ?>>
                    state) {
              DeclarationComposite<?, ?> declarationComposite = state.getValue();
              if (declarationComposite != null) {
                return edu.cmu.cs.dennisc.java.lang.ClassUtilities.getInstance(
                    declarationComposite.getType(), org.lgna.project.ast.NamedUserType.class);
              } else {
                return null;
              }
            }
          };
    }
    return this.typeMetaState;
  }

  public org.alice.ide.iconfactory.IconFactoryManager getIconFactoryManager() {
    return this.iconFactoryManager;
  }

  public org.alice.ide.declarationseditor.DeclarationsEditorComposite
      getDeclarationsEditorComposite() {
    return this.declarationsEditorComposite;
  }

  public org.lgna.croquet.Operation getResourcesDialogLaunchOperation() {
    return this.resourcesDialogLaunchOperation;
  }

  public org.lgna.croquet.BooleanState getStasticsFrameIsShowingState() {
    return this.stasticsFrameIsShowingState;
  }

  private static final Integer HIGHLIGHT_STENCIL_LAYER = javax.swing.JLayeredPane.POPUP_LAYER - 2;

  @Deprecated
  public org.alice.ide.highlight.IdeHighlightStencil getHighlightStencil() {
    if (this.highlightStencil != null) {
      // pass
    } else {
      this.highlightStencil =
          new org.alice.ide.highlight.IdeHighlightStencil(this.getFrame(), HIGHLIGHT_STENCIL_LAYER);
    }
    return this.highlightStencil;
  }

  // <lg>

  @Deprecated
  public void showHighlightStencil(
      final org.lgna.croquet.views.TrackableShape trackableShape, String noteText) {
    org.alice.ide.highlight.IdeHighlightStencil highlightStencil = this.getHighlightStencil();
    highlightStencil.showHighlightOverTrackableShape(trackableShape, noteText);
  }

  // </lg>

  public org.lgna.project.ast.AbstractCode getFocusedCode() {
    org.lgna.project.ast.AbstractDeclaration declaration =
        this.getMetaDeclarationFauxState().getValue();
    if (declaration instanceof org.lgna.project.ast.AbstractCode) {
      return (org.lgna.project.ast.AbstractCode) declaration;
    } else {
      return null;
    }
  }

  public void setFocusedCode(org.lgna.project.ast.AbstractCode nextFocusedCode) {
    this.selectDeclaration(nextFocusedCode);
  }

  public void selectDeclarationComposite(
      org.alice.ide.declarationseditor.DeclarationComposite declarationComposite) {
    if (declarationComposite != null) {
      org.lgna.project.ast.AbstractDeclaration declaration = declarationComposite.getDeclaration();
      //			org.lgna.project.ast.AbstractType<?, ?, ?> type;
      //			if( declaration instanceof org.lgna.project.ast.AbstractType<?, ?, ?> ) {
      //				type = (org.lgna.project.ast.AbstractType<?, ?, ?>)declaration;
      //			} else if( declaration instanceof org.lgna.project.ast.AbstractCode ) {
      //				org.lgna.project.ast.AbstractCode code = (org.lgna.project.ast.AbstractCode)declaration;
      //				type = code.getDeclaringType();
      //			} else {
      //				type = null;
      //			}
      //			if( type instanceof org.lgna.project.ast.NamedUserType ) {
      //				org.alice.ide.declarationseditor.TypeState.getInstance().setValueTransactionlessly(
      // (org.lgna.project.ast.NamedUserType)type );
      //			}
      org.alice.ide.declarationseditor.DeclarationTabState tabState =
          this.getDeclarationsEditorComposite().getTabState();
      //			if( tabState.containsItem( declarationComposite ) ) {
      //				//pass
      //			} else {
      //				tabState.addItem( declarationComposite );
      //			}
      tabState.setValueTransactionlessly(declarationComposite);
    }
  }

  private void selectDeclaration(org.lgna.project.ast.AbstractDeclaration declaration) {
    this.selectDeclarationComposite(
        org.alice.ide.declarationseditor.DeclarationComposite.getInstance(declaration));
  }

  public org.lgna.croquet.Operation getNewProjectOperation() {
    return this.newProjectOperation;
  }

  public org.lgna.croquet.Operation getOpenProjectOperation() {
    return this.openProjectOperation;
  }

  public org.lgna.croquet.Operation getUndoOperation() {
    return this.undoOperation;
  }

  public org.lgna.croquet.Operation getRedoOperation() {
    return this.redoOperation;
  }

  private final ApiConfigurationManager apiConfigurationManager;

  private org.lgna.croquet.meta.MetaState<org.lgna.project.ast.NamedUserType> typeMetaState;

  private final org.lgna.croquet.Operation[] uploadOperations;

  private final org.alice.ide.croquet.models.project.find.croquet.FindComposite findComposite;

  private final org.alice.ide.perspectives.noproject.NoProjectPerspective noProjectPerspective;
  private final org.alice.stageide.perspectives.CodePerspective codePerspective;
  private final org.alice.stageide.perspectives.SetupScenePerspective setupScenePerspective;
  private final org.alice.ide.croquet.models.AliceMenuBar aliceMenuBar;

  private final org.alice.stageide.perspectives.PerspectiveState perspectiveState;

  private final MetaDeclarationFauxState metaDeclarationFauxState;

  private final org.alice.ide.instancefactory.croquet.InstanceFactoryState instanceFactoryState;

  private final org.alice.ide.iconfactory.IconFactoryManager iconFactoryManager;

  private final org.alice.ide.declarationseditor.DeclarationsEditorComposite
      declarationsEditorComposite =
          new org.alice.ide.declarationseditor.DeclarationsEditorComposite();

  private final org.lgna.croquet.Operation resourcesDialogLaunchOperation =
      org.lgna.croquet.imp.launch.LazySimpleLaunchOperationFactory.createInstance(
              org.alice.ide.resource.manager.ResourceManagerComposite.class,
              new edu.cmu.cs.dennisc.pattern.Lazy<
                  org.alice.ide.resource.manager.ResourceManagerComposite>() {
                @Override
                protected org.alice.ide.resource.manager.ResourceManagerComposite create() {
                  return new org.alice.ide.resource.manager.ResourceManagerComposite(
                      ProjectDocumentFrame.this);
                }
              },
              org.lgna.croquet.Application.DOCUMENT_UI_GROUP)
          .getLaunchOperation();

  private final org.lgna.croquet.BooleanState stasticsFrameIsShowingState =
      org.lgna.croquet.imp.frame.LazyIsFrameShowingState.createInstance(
          org.lgna.croquet.Application.INFORMATION_GROUP,
          org.alice.ide.croquet.models.project.stats.croquet.StatisticsFrameComposite.class,
          new edu.cmu.cs.dennisc.pattern.Lazy<
              org.alice.ide.croquet.models.project.stats.croquet.StatisticsFrameComposite>() {
            @Override
            protected org.alice.ide.croquet.models.project.stats.croquet.StatisticsFrameComposite
                create() {
              return new org.alice.ide.croquet.models.project.stats.croquet
                  .StatisticsFrameComposite(ProjectDocumentFrame.this);
            }
          });
  private final edu.cmu.cs.dennisc.java.util.DStack<
          org.alice.ide.ReasonToDisableSomeAmountOfRendering>
      stack = edu.cmu.cs.dennisc.java.util.Stacks.newStack();

  private final java.util.Map<
          org.lgna.project.ast.AbstractCode, org.alice.ide.instancefactory.InstanceFactory>
      mapCodeToInstanceFactory = edu.cmu.cs.dennisc.java.util.Maps.newHashMap();
  private final org.lgna.croquet.event.ValueListener<org.alice.ide.instancefactory.InstanceFactory>
      instanceFactoryListener =
          new org.lgna.croquet.event.ValueListener<
              org.alice.ide.instancefactory.InstanceFactory>() {
            @Override
            public void valueChanged(
                org.lgna.croquet.event.ValueEvent<org.alice.ide.instancefactory.InstanceFactory>
                    e) {
              org.alice.ide.instancefactory.InstanceFactory nextValue = e.getNextValue();
              if (nextValue != null) {
                org.lgna.project.ast.AbstractCode code = getFocusedCode();
                if (code != null) {
                  mapCodeToInstanceFactory.put(code, nextValue);
                }
              }
            }
          };

  private final org.lgna.croquet.event.ValueListener<org.alice.ide.formatter.Formatter>
      formatterListener =
          new org.lgna.croquet.event.ValueListener<org.alice.ide.formatter.Formatter>() {
            @Override
            public void valueChanged(
                org.lgna.croquet.event.ValueEvent<org.alice.ide.formatter.Formatter> e) {
              edu.cmu.cs.dennisc.java.awt.ComponentUtilities.revalidateTree(
                  getFrame().getAwtComponent());
            }
          };

  private org.alice.ide.highlight.IdeHighlightStencil highlightStencil;

  private final org.lgna.croquet.Operation newProjectOperation =
      new org.alice.ide.croquet.models.projecturi.NewProjectOperation(this);
  private final org.lgna.croquet.Operation openProjectOperation =
      new org.alice.ide.croquet.models.projecturi.OpenProjectOperation(this);

  private final org.lgna.croquet.Operation undoOperation =
      new org.alice.ide.croquet.models.history.UndoOperation(this);
  private final org.lgna.croquet.Operation redoOperation =
      new org.alice.ide.croquet.models.history.RedoOperation(this);
}
/**
 * An object that receives <code>VMObserver</code> events from the <code>VMObserverManager</code>,
 * processes these events, creates the appropriate <code>EventNode</code>, and then notifies any
 * listeners of statement execution.
 *
 * @author Paul Gross
 */
public class VMExecutionObserver implements VMObserver {

  private boolean isRecording = false;
  private boolean hasInvokedEntryPoint = false;

  private final Map<Object, UserField> instanceMap = Maps.newHashMap();
  private final Set<CurrentExecutionListener> executionListeners = Sets.newHashSet();

  private final java.util.Map<Statement, java.util.ArrayList<AbstractEventNode<?>>>
      statementEventNodes = Maps.newHashMap();
  private final java.util.List<LambdaEventNode> lambdaEventNodes = Lists.newLinkedList();

  private final KeyedStack<ComponentThread, AbstractEventNode<?>> eventNodeStack =
      new KeyedStack<ComponentThread, AbstractEventNode<?>>();
  private AbstractEventNode<?> rootEventNode;
  private UserInstance sceneInstance;

  public VMExecutionObserver(MessagingVirtualMachine virtualMachine) {
    virtualMachine.addObserver(this);
  }

  public void addCurrentExecutionListener(CurrentExecutionListener listener) {
    synchronized (this.executionListeners) {
      this.executionListeners.add(listener);
    }
  }

  public boolean removeCurrentExecutionListener(CurrentExecutionListener listener) {
    synchronized (this.executionListeners) {
      return this.executionListeners.remove(listener);
    }
  }

  public UserInstance getSceneInstance() {
    return this.sceneInstance;
  }

  public boolean eventNodesExistForStatement(Statement statement) {
    java.util.Collection<AbstractEventNode<?>> nodes = this.statementEventNodes.get(statement);
    return (nodes != null) && (nodes.size() > 0);
  }

  public java.util.ArrayList<AbstractEventNode<?>> getEventNodesForStatement(Statement statement) {
    return this.statementEventNodes.get(statement);
  }

  public AbstractEventNode<?> getEventNodeForStatementAtTime(Statement statement, double time) {
    for (AbstractEventNode<?> event : getEventNodesForStatement(statement)) {
      if (event.isExecutingAtTime(time)) {
        return event;
      }
    }
    return null;
  }

  public boolean isRootEventNode(AbstractEventNode<?> eventNode) {
    return (eventNode == this.rootEventNode);
  }

  public AbstractEventNode<?> getRootEventNode() {
    return this.rootEventNode;
  }

  public java.util.List<LambdaEventNode> getLambdaEventNodes() {
    return this.lambdaEventNodes;
  }

  public Map<Object, UserField> getInstanceMap() {
    return this.instanceMap;
  }

  public boolean hasInvokedEntryPoint() {
    return this.hasInvokedEntryPoint;
  }

  public boolean isRecording() {
    return this.isRecording;
  }

  public void setIsRecording(boolean value) {
    this.isRecording = value;
  }

  /**
   * Called when a new {@link VMObservableEvent} is passed. Executes the correct method based on the
   * {@link VMMessage} for the event.
   *
   * @param event {@link VMObservableEvent} passed from {@link VMObserverManager}
   */
  @Override
  public synchronized void update(VMObservableEvent event) {
    VMMessage eventMsg = event.getVMMessage();

    if (eventMsg == VMMessage.START_RECORDING) {
      this.isRecording = true;
    } else if (eventMsg == VMMessage.STOP_RECORDING) {
      this.isRecording = false;
    } else if (eventMsg == VMMessage.CREATE_INSTANCE) {
      if ((event.getProperties().length == 1) && (event.getProperties()[0] instanceof UserField)) {
        UserField field = (UserField) event.getProperties()[0];
        Object instance = event.getObject();

        if (instance instanceof UserInstance) {
          if (((UserInstance) instance).getType().isAssignableTo(SScene.class)) {
            this.sceneInstance = (UserInstance) instance;
          }

          instance = ((UserInstance) instance).getJavaInstance();
        }

        this.instanceMap.put(instance, field);
      }
    }

    // Lambda invocations come from eventListeners and are
    // recorded outside the default record loop
    if (eventMsg == VMMessage.START_LAMBDA_INVOKE) {
      if (event.getObject() instanceof UserLambda) {
        startLambdaInvocation(event);
      } else {
        throw new ExecutionObserverException(event);
      }
    } else if (eventMsg == VMMessage.END_LAMBDA_INVOKE) {
      if (event.getObject() instanceof UserLambda) {
        endLambdaInvocation(event);
      } else {
        throw new ExecutionObserverException(event);
      }
    }

    // Should we record this message?
    boolean shouldRecord = isRecording();
    if (!shouldRecord) {
      if (event.getThread() != null) {
        shouldRecord = event.getThread().getDescription().contentEquals("eventThread");
      }
    }

    synchronized (this) {
      if (shouldRecord) {
        switch (eventMsg) {
          case RESET:
            reset();
            break;
          case START_INVOKE_ENTRY_POINT:
            this.hasInvokedEntryPoint = true;
            break;
          case START_EXP_EVAL:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof Expression) {

                // Non-function method invocations are handled separately
                if (event.getObject() instanceof MethodInvocation) {
                  MethodInvocation methodInvocation = (MethodInvocation) event.getObject();
                  if (methodInvocation.method.getValue().isFunction()) {
                    startExpressionEvaluation(event);
                  }
                } else {
                  startExpressionEvaluation(event);
                }
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case END_EXP_EVAL:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof Expression) {

                // Non-function method invocations are handled separately
                if (event.getObject() instanceof MethodInvocation) {
                  MethodInvocation methodInvocation = (MethodInvocation) event.getObject();
                  if (methodInvocation.method.getValue().isFunction()) {
                    endExpressionEvaluation(event);
                  }
                } else {
                  endExpressionEvaluation(event);
                }
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case START_METHOD_INVOKE:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof MethodInvocation) {
                startMethodInvocation(event);
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case START_STMT_EXEC:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof BlockStatement) {
                // pass
                //						} else if( event.getObject() instanceof Comment ) {
                //							//pass
                //						} else {
              } else {
                startStatement(event);
              }
            }
            break;
          case END_STMT_EXEC:
            if (this.hasInvokedEntryPoint) {
              if (event.getObject() instanceof BlockStatement) {
                //							//pass
                //						} else if( event.getObject() instanceof Comment ) {
                //							//pass
                //						} else {
              } else {
                endStatement(event);
              }
            }
            break;
          case START_CONTAINER:
            if (this.hasInvokedEntryPoint) {
              if ((event.getObject() instanceof AbstractStatementWithBody)
                  || (event.getObject() instanceof BlockStatement)) {
                startContainer(event);
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
          case END_CONTAINER:
            if (this.hasInvokedEntryPoint) {
              if ((event.getObject() instanceof AbstractStatementWithBody)
                  || (event.getObject() instanceof BlockStatement)) {
                endContainer(event);
              } else {
                throw new ExecutionObserverException(event);
              }
            }
            break;
        }
      }
    }
  }

  private void startLambdaInvocation(VMObservableEvent event) {
    org.lgna.project.ast.UserLambda lambda = (UserLambda) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();
    org.lgna.project.ast.AbstractMethod method = (AbstractMethod) event.getProperties()[0];

    // We ignore scene activation
    if (!method.getName().contentEquals("sceneActivated")) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(lambda, thread, startTime, null);
      ((LambdaEventNode) eventNode).setInvokingEventMethod(method);

      pushEventNode(eventNode, false);
    }
  }

  private void endLambdaInvocation(VMObservableEvent event) {
    org.lgna.project.ast.UserLambda lambda = (UserLambda) event.getObject();
    org.lgna.project.ast.AbstractMethod method = (AbstractMethod) event.getProperties()[0];
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();

    if (!method.getName().contentEquals("sceneActivated")) {
      AbstractEventNode<?> eventNode = popEventNode(thread, false);

      lambdaEventNodes.add((LambdaEventNode) eventNode);

      if ((eventNode != null)
          && (eventNode.getAstNode() == lambda)
          && (eventNode.getThread() == thread)) {
        eventNode.setEndTime(endTime);
      } else {
        throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
      }
    }
  }

  /**
   * Handles the beginning of evaluation of an {@link Expression}.
   *
   * @param event VMObservableEvent event for expression
   */
  private void startExpressionEvaluation(VMObservableEvent event) {
    Expression expression = (Expression) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();

    AbstractEventNode<?> parentNode = peekEventNode(thread);

    ExpressionEvaluationEventNode eventNode =
        (ExpressionEvaluationEventNode)
            EventNodeFactory.createEventNode(expression, thread, startTime, parentNode);

    // Add expression evaluation to ExpressionStatementEventNode
    if (parentNode instanceof ExpressionStatementEventNode) {
      ((ExpressionStatementEventNode) parentNode).addExpressionEvaluationNode(eventNode);
    }
    // Handle expression evaluation for ConditionalEventNode
    else if (parentNode instanceof ConditionalStatementEventNode) {
      ((ConditionalStatementEventNode) parentNode).addConditionalEvaluation(eventNode);
    }
    // Handle expression evaluation for CountLoopEventNode
    else if (parentNode instanceof CountLoopEventNode) {
      ((CountLoopEventNode) parentNode).setCountExpressionNode(eventNode);
    }
    // Handle expression evaluation for EachInArrayTogetherEventNode
    else if (parentNode instanceof EachInArrayTogetherEventNode) {
      ((EachInArrayTogetherEventNode) parentNode).setArrayExpressionNode(eventNode);
    }
    // Handle expression evaluation for ForEachInArrayLoopEventNode
    else if (parentNode instanceof ForEachInArrayLoopEventNode) {
      ((ForEachInArrayLoopEventNode) parentNode).setArrayExpressionNode(eventNode);
    }
    // Handle expression evaluation for WhileLoopEventNode
    else if (parentNode instanceof WhileLoopEventNode) {
      ((WhileLoopEventNode) parentNode).addConditionalEvaluation(eventNode);
    }
    // Handle expression evaluation for LocalDeclarationStatementEventNode
    else if (parentNode instanceof LocalDeclarationStatementEventNode) {
      ((LocalDeclarationStatementEventNode) parentNode).setInitializerExpression(eventNode);
    }
    // Handle expression evaluation for ReturnStatementEventNode
    else if (parentNode instanceof ReturnStatementEventNode) {
      ((ReturnStatementEventNode) parentNode).setExpressionNode(eventNode);
    }
    // Handle expression evaluation for ExpressionEvaluationEventNode
    else if (parentNode instanceof ExpressionEvaluationEventNode) {
      // pass - handled on endExpressionEvaluation
    } else {
      throw new ExecutionObserverException("Parent node invalid:" + parentNode.toString(), event);
    }

    pushEventNode(eventNode, false);
  }

  /**
   * Handles the ending of evaluation of an {@link Expression}.
   *
   * @param event VMObservableEvent event for expression
   */
  private void endExpressionEvaluation(VMObservableEvent event) {
    Expression expression = (Expression) event.getObject();
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();
    Object value = event.getProperties()[0];

    AbstractEventNode<?> eventNode = popEventNode(thread, false);

    if ((eventNode != null)
        && (eventNode.getAstNode() == expression)
        && (eventNode.getThread() == thread)) {
      eventNode.setEndTime(endTime);
      ((ExpressionEvaluationEventNode) eventNode).setValue(value);

      UserField field = this.getUserFieldForInstance(value);
      if (field != null) {
        ((ExpressionEvaluationEventNode) eventNode).setValueField(field);
      }
    } else {
      throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
    }
  }

  /**
   * Handles beginning execution of a {@link Statement}.
   *
   * @param event {@link VMObservableEvent} event for statement
   */
  private void startStatement(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();

    AbstractEventNode<?> parentNode = peekEventNode(getThreadForNode(statement, thread));

    // A Statement should always have a parent node (which can only be a ContainerEventNode)
    if (parentNode instanceof ContainerEventNode) {
      ContainerEventNode<?> containerNode = (ContainerEventNode<?>) parentNode;
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, containerNode);

      pushEventNode(eventNode, false);
    } else {
      throw new ExecutionObserverException("Parent node invalid: " + parentNode.toString(), event);
    }
  }

  /**
   * Handles ending execution of a {@link Statement}.
   *
   * @param event {@link VMObservableEvent} event for statement
   */
  private void endStatement(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();

    AbstractEventNode<?> eventNode = popEventNode(thread, true);

    if ((eventNode != null)
        && (eventNode.getAstNode() == statement)
        && (eventNode.getThread() == thread)) {
      eventNode.setEndTime(endTime);
    } else {
      throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
    }
  }

  /**
   * Handles beginning execution of container statement ( {@link AbstractStatementWithBody} or
   * {@link BlockStatement}).
   *
   * @param event {@link VMObservableEvent} event for container type statement
   */
  private void startContainer(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double startTime = event.getTime();

    AbstractEventNode<?> parentNode = peekEventNode(getThreadForNode(statement, thread));

    // Root case
    if (parentNode == null) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, null);
      this.rootEventNode = eventNode;
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ConditionalStatementEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ContainerEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ExpressionStatementEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      ((ExpressionStatementEventNode) parentNode)
          .addUserMethodEventNode((ContainerEventNode<?>) eventNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof ExpressionEvaluationEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      pushEventNode(eventNode, true);
    } else if (parentNode instanceof LambdaEventNode) {
      AbstractEventNode<?> eventNode =
          EventNodeFactory.createEventNode(statement, thread, startTime, parentNode);
      ((LambdaEventNode) parentNode).addBodyEventNode((ContainerEventNode<?>) eventNode);
      pushEventNode(eventNode, true);
    } else {
      throw new ExecutionObserverException("Parent node invalid: " + parentNode.toString(), event);
    }
  }

  /**
   * Handles ending execution of Container statement ( {@link AbstractStatementWithBody} or {@link
   * BlockStatement}).
   *
   * @param event {@link VMObservableEvent} event for container type statement
   */
  private void endContainer(VMObservableEvent event) {
    Statement statement = (Statement) event.getObject();
    ComponentThread thread = event.getThread();
    double endTime = event.getTime();

    AbstractEventNode<?> eventNode = popEventNode(thread, true);

    if ((eventNode != null)
        && (eventNode.getAstNode() == statement)
        && (eventNode.getThread() == thread)) {
      eventNode.setEndTime(endTime);
    } else {
      throw new ExecutionObserverException("EventNode invalid: " + eventNode.toString(), event);
    }
  }

  /**
   * Handles beginning execution of {@link MethodInvocation}.
   *
   * @param event {@link VMObservableEvent} event for invocation
   */
  private void startMethodInvocation(VMObservableEvent event) {
    MethodInvocation invocation = (MethodInvocation) event.getObject();
    ComponentThread thread = event.getThread();
    Object instance = event.getProperties()[1];

    UserField field = getUserFieldForInstance(instance);

    AbstractEventNode<?> parentNode = peekEventNode(getThreadForNode(invocation, thread));

    // Add instance and referencing field to parent node
    if (parentNode instanceof ExpressionStatementEventNode) {
      ExpressionStatementEventNode statementNode = (ExpressionStatementEventNode) parentNode;
      if ((instance != null) & (field != null)) {
        statementNode.setCallerInstance(instance);
        statementNode.setCallerField(field);
      }

      if ((instance != null) & (field != null)) {
        notifyListenersOfStart(statementNode); // Now that caller has been set, notify listeners
      }

      if (invocation.method.getValue() instanceof UserMethod) {
        UserMethod userMethod = (UserMethod) invocation.method.getValue();
        if (!userMethod.isFunction()) {
          statementNode.setUserMethod(userMethod);
        }
      }
    } else if (parentNode instanceof ExpressionEvaluationEventNode) {
      if (field != null) {
        ((ExpressionEvaluationEventNode) parentNode).setValueField(field);
      }
    }
  }

  /**
   * Clears all maps involved in tracking. Note: this method does <i>not</i> remove <code>
   * CurrentExecutionListeners</code>.
   */
  private void reset() {
    statementEventNodes.clear();
    eventNodeStack.clear();
  }

  /**
   * Pushes an {@link AbstractEventNode<?>} onto the stack and executes associated functions.
   *
   * @param eventNode <code>AbstractEventNode<?></code> to push onto the stack
   * @param shouldNotify boolean dictating whether listeners should be notified of push
   */
  private void pushEventNode(AbstractEventNode<?> eventNode, boolean shouldNotify) {
    ArrayList<AbstractEventNode<?>> eventNodes =
        this.statementEventNodes.get(eventNode.getAstNode());
    if (eventNodes == null) {
      eventNodes = new ArrayList<AbstractEventNode<?>>();
      if (eventNode.getAstNode() instanceof Statement) {
        this.statementEventNodes.put((Statement) eventNode.getAstNode(), eventNodes);
      }
    }
    eventNodes.add(eventNode);
    this.eventNodeStack.push(eventNode, eventNode.getThread());

    if (shouldNotify) {
      notifyListenersOfStart(eventNode);
    }
  }

  /**
   * Peeks {@link AbstractEventNode<?>} from top of stack for given thread.
   *
   * @param thread {@link ComponentThread} to check stack for
   * @return AbstractEventNode<?> on top of stack
   */
  private AbstractEventNode<?> peekEventNode(ComponentThread thread) {
    return this.eventNodeStack.peek(thread);
  }

  /**
   * Removes and returns {@link AbstractEventNode<?>} from top of stack for a given thread.
   *
   * @param thread {@link ComponentThread} to remove stack for
   * @return <code>AbstractEventNode<?></code> removed from stack
   */
  private AbstractEventNode<?> popEventNode(ComponentThread thread, boolean shouldNotify) {
    AbstractEventNode<?> rv = this.eventNodeStack.pop(thread);

    if (shouldNotify) {
      notifyListenersOfEnd(rv);
    }
    return rv;
  }

  /**
   * Gets the correct thread for lookup in {@link KeyedStack}. In the instance of parallel execution
   * (<code>DoTogether</code> and <code>EachInArrayTogether</code> statements) we want to get the
   * parent thread.
   *
   * @param astNode {@link Node} in AST to check
   * @param thread {@link ComponentThread} to reference
   * @return the correct <code>ComponentThread</code> for lookup
   */
  private ComponentThread getThreadForNode(Node astNode, ComponentThread thread) {
    if (astNode.getParent() instanceof BlockStatement) {
      BlockStatement blockStatement = (BlockStatement) astNode.getParent();
      if (blockStatement.getParent() instanceof DoTogether) {
        DoTogether doTogether = (DoTogether) blockStatement.getParent();

        // special case for DoTogether with only one statement
        if (doTogether.body.getValue().statements.size() > 1) {
          return thread.getParentThread();
        } else {
          return thread;
        }
      } else {
        return thread;
      }
    } else if (astNode.getParent() instanceof EachInArrayTogether) {
      return thread.getParentThread();
    } else {
      return thread;
    }
  }

  private void notifyListenersOfStart(AbstractEventNode<?> eventNode) {
    synchronized (this.executionListeners) {
      for (CurrentExecutionListener listener : this.executionListeners) {
        listener.startingExecution(eventNode);
      }
    }
  }

  private void notifyListenersOfEnd(AbstractEventNode<?> eventNode) {
    synchronized (this.executionListeners) {
      for (CurrentExecutionListener listener : this.executionListeners) {
        listener.endingExecution(eventNode);
      }
    }
  }

  /**
   * Based on provided <code>Object</code>, finds the applicable field set during instance creation.
   *
   * @param caller object evaluated by vm
   * @return mapped <code>UserField</code> for value
   */
  private UserField getUserFieldForInstance(Object instance) {
    if (instance instanceof UserInstance) {
      instance = ((UserInstance) instance).getJavaInstance();
    }
    // Get instance from invocation - special case for joints
    else if (instance instanceof org.lgna.story.SJoint) {
      org.lgna.story.SJoint joint = (org.lgna.story.SJoint) instance;

      org.lgna.story.implementation.JointImp jointImp =
          org.lgna.story.EmployeesOnly.getImplementation(joint);
      org.lgna.story.implementation.JointedModelImp<?, ?> jointedModelImp =
          jointImp.getJointedModelParent();
      org.lgna.story.SJointedModel jointedModel = jointedModelImp.getAbstraction();
      instance = jointedModel;
    }

    return this.instanceMap.get(instance);
  }

  /**
   * Specialized stack-like structure that allows for multiple stacks based on a key. Each key
   * passed into the <code>KeyedStack</code> is given a unique <code>Stack</code> and all
   * interactions with the <code>KeyedStack</code> require said key.
   *
   * @author Michael Pogran
   */
  private class KeyedStack<K, V> {
    private HashMap<K, java.util.Stack<V>> stackMap = Maps.newHashMap();

    public V push(V value, K key) {
      java.util.Stack<V> stack = this.stackMap.get(key);
      if (stack == null) {
        stack = new java.util.Stack<V>();
        this.stackMap.put(key, stack);
      }
      return stack.push(value);
    }

    public V peek(K key) {
      java.util.Stack<V> stack = this.stackMap.get(key);
      if (stack != null) {
        return stack.peek();
      } else {
        return null;
      }
    }

    public V pop(K key) {
      java.util.Stack<V> stack = this.stackMap.get(key);
      if (stack != null) {
        return stack.pop();
      } else {
        return null;
      }
    }

    public void clear() {
      stackMap = edu.cmu.cs.dennisc.java.util.Maps.newHashMap();
    }
  }
}
 public void clear() {
   stackMap = edu.cmu.cs.dennisc.java.util.Maps.newHashMap();
 }
Exemple #8
0
/** @author Dennis Cosgrove */
public class Decoder {
  private static class ClassReflectionProxyAndMethodName {
    private final ClassReflectionProxy classReflectionProxy;
    private final String name;

    public ClassReflectionProxyAndMethodName(
        ClassReflectionProxy classReflectionProxy, String name) {
      this.classReflectionProxy = classReflectionProxy;
      this.name = name;
    }

    public ClassReflectionProxy getClassReflectionProxy() {
      return this.classReflectionProxy;
    }

    public String getName() {
      return this.name;
    }
  }

  private static final edu.cmu.cs.dennisc.map.MapToMap<
          ClassReflectionProxy, String, ClassReflectionProxyAndMethodName>
      betweenClassesMethodMap = edu.cmu.cs.dennisc.map.MapToMap.newInstance();
  private static final edu.cmu.cs.dennisc.map.MapToMap<ClassReflectionProxy, String, String>
      intraClassMethodMap = edu.cmu.cs.dennisc.map.MapToMap.newInstance();
  private static final java.util.Map<String, String> mapClassNameToClassName =
      edu.cmu.cs.dennisc.java.util.Maps.newHashMap();

  public static void addMethodFilterWithinClass(
      ClassReflectionProxy classReflectionProxy, String prevName, String nextName) {
    Decoder.intraClassMethodMap.put(classReflectionProxy, prevName, nextName);
  }

  public static void addMethodFilterWithinClass(Class<?> cls, String prevName, String nextName) {
    addMethodFilterWithinClass(
        JavaType.getInstance(cls).getClassReflectionProxy(), prevName, nextName);
  }

  public static void addMethodFilterBetweenClasses(
      ClassReflectionProxy prevClassReflectionProxy,
      String prevName,
      ClassReflectionProxy nextClassReflectionProxy,
      String nextName) {
    Decoder.betweenClassesMethodMap.put(
        prevClassReflectionProxy,
        prevName,
        new ClassReflectionProxyAndMethodName(nextClassReflectionProxy, nextName));
  }

  public static void addMethodFilterBetweenClasses(
      Class<?> prevCls, String prevName, Class<?> nextCls, String nextName) {
    addMethodFilterBetweenClasses(
        JavaType.getInstance(prevCls).getClassReflectionProxy(),
        prevName,
        JavaType.getInstance(nextCls).getClassReflectionProxy(),
        nextName);
  }

  public static void addClassFilter(
      ClassReflectionProxy prevClassReflectionProxy,
      ClassReflectionProxy nextClassReflectionProxy) {
    Decoder.mapClassNameToClassName.put(
        prevClassReflectionProxy.getName(), nextClassReflectionProxy.getName());
  }

  public static void addClassFilter(
      ClassReflectionProxy prevClassReflectionProxy, Class<?> nextCls) {
    addClassFilter(
        prevClassReflectionProxy, JavaType.getInstance(nextCls).getClassReflectionProxy());
  }

  public static void addClassFilter(Class<?> prevCls, Class<?> nextCls) {
    addClassFilter(
        JavaType.getInstance(prevCls).getClassReflectionProxy(),
        JavaType.getInstance(nextCls).getClassReflectionProxy());
  }

  private static String filterClassNameIfNecessary(String clsName) {
    String rv = Decoder.mapClassNameToClassName.get(clsName);
    if (rv != null) {
      // pass
    } else {
      rv = clsName;
    }
    return rv;
  }

  //	private static String filterMethodNameIfNecessary( ClassReflectionProxy classReflectionProxy,
  // String name ) {
  //		String rv = Decoder.intraClassMethodMap.get( classReflectionProxy, name );
  //		if( rv != null ) {
  //			//pass
  //		} else {
  //			rv = name;
  //		}
  //		return rv;
  //	}

  public Decoder(
      org.lgna.project.Version srcVersion,
      org.lgna.project.Version dstVersion,
      DecodeIdPolicy policy) {
    this.srcVersion = srcVersion;
    this.dstVersion = dstVersion;
    this.policy = policy;
  }

  private static ClassReflectionProxy createClassReflectionProxy(String clsName) {
    return new ClassReflectionProxy(filterClassNameIfNecessary(clsName));
  }

  private String getClassName(org.w3c.dom.Element xmlElement) {
    String rv = xmlElement.getAttribute(CodecConstants.TYPE_ATTRIBUTE);
    return rv;
  }

  private ClassReflectionProxy getJavaClassInfo(org.w3c.dom.Element xmlElement) {
    return createClassReflectionProxy(getClassName(xmlElement));
  }

  // todo: investigate
  private Class<?> getCls(org.w3c.dom.Element xmlElement) {
    return edu.cmu.cs.dennisc.java.lang.reflect.ReflectionUtilities.getClassForName(
        getClassName(xmlElement));
  }

  // todo: investigate
  private Object newInstance(org.w3c.dom.Element xmlElement) {
    return edu.cmu.cs.dennisc.java.lang.reflect.ReflectionUtilities.newInstance(
        getClassName(xmlElement));
  }

  public Object decodeValue(
      org.w3c.dom.Element xmlValue, java.util.Map<Integer, AbstractDeclaration> map) {
    Object rv;
    if (xmlValue.hasAttribute("isNull")) {
      rv = null;
    } else {
      String tagName = xmlValue.getTagName();
      if (tagName.equals("node")) {
        try {
          rv = decode(xmlValue, map);
        } catch (RuntimeException re) {
          re.printStackTrace();
          // rv = new NullLiteral();
          rv = null;
        }
      } else if (tagName.equals("collection")) {
        java.util.Collection collection = (java.util.Collection) newInstance(xmlValue);
        org.w3c.dom.NodeList nodeList = xmlValue.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
          org.w3c.dom.Element xmlItem = (org.w3c.dom.Element) nodeList.item(i);
          collection.add(decodeValue(xmlItem, map));
        }
        rv = collection;
        //			} else if( tagName.equals( "resource" ) ) {
        //				String uuidText = xmlValue.getAttribute( CodecConstants.UUID_ATTRIBUTE );
        //				edu.cmu.cs.dennisc.print.PrintUtilities.println( "uuidText", uuidText );
        //				java.util.UUID uuid = java.util.UUID.fromString( uuidText );
        //				edu.cmu.cs.dennisc.print.PrintUtilities.println( "uuid", uuid );
        //				rv = org.alice.virtualmachine.Resource.get( uuid );
      } else if (tagName.equals("value")) {
        Class<?> cls = getCls(xmlValue);
        String textContent = xmlValue.getTextContent();
        if (cls.equals(String.class)) {
          rv = textContent;
        } else {
          try {
            rv = edu.cmu.cs.dennisc.java.lang.reflect.ReflectionUtilities.valueOf(cls, textContent);
          } catch (RuntimeException re) {
            if ("DIVIDE".equals(textContent)) {
              rv =
                  edu.cmu.cs.dennisc.java.lang.reflect.ReflectionUtilities.valueOf(
                      cls, "REAL_DIVIDE");
            } else if ("REMAINDER".equals(textContent)) {
              rv =
                  edu.cmu.cs.dennisc.java.lang.reflect.ReflectionUtilities.valueOf(
                      cls, "REAL_REMAINDER");
            } else {
              throw re;
            }
          }
        }
      } else {
        throw new RuntimeException();
      }
    }
    return rv;
  }

  private ClassReflectionProxy decodeType(org.w3c.dom.Element xmlElement, String nodeName) {
    org.w3c.dom.Element xmlClass =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(xmlElement, nodeName);
    String clsName = xmlClass.getAttribute("name");
    return createClassReflectionProxy(clsName);
  }

  private UserArrayType decodeUserArrayType(
      org.w3c.dom.Element xmlElement, java.util.Map<Integer, AbstractDeclaration> map) {
    org.w3c.dom.Element xmlLeafType =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(xmlElement, "leafType");
    org.w3c.dom.Element xmlDimensionCount =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(
            xmlElement, "dimensionCount");
    org.w3c.dom.Element xmlLeafTypeNode =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(xmlLeafType, "node");

    org.w3c.dom.Node xmlLeafTypeFirstChild = xmlLeafType.getFirstChild();
    if (xmlLeafTypeFirstChild instanceof org.w3c.dom.Element) {
      org.w3c.dom.Element xmlLeafTypeFirstChildElement =
          (org.w3c.dom.Element) xmlLeafTypeFirstChild;
      if (xmlLeafTypeFirstChildElement.hasAttribute(CodecConstants.UNIQUE_KEY_ATTRIBUTE)) {
        int arrayTypeUniqueKey = getUniqueKey(xmlElement);
        int leafTypeUniqueKey = getUniqueKey(xmlLeafTypeFirstChildElement);
        EPIC_HACK_mapArrayTypeKeyToLeafTypeKey.put(arrayTypeUniqueKey, leafTypeUniqueKey);
      }
    }

    NamedUserType leafType = (NamedUserType) decode(xmlLeafTypeNode, map);
    int dimensionCount = Integer.parseInt(xmlDimensionCount.getTextContent());
    return UserArrayType.getInstance(leafType, dimensionCount);
  }

  private AnonymousUserConstructor decodeAnonymousConstructor(
      org.w3c.dom.Element xmlElement, java.util.Map<Integer, AbstractDeclaration> map) {
    org.w3c.dom.Element xmlLeafType =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(
            xmlElement, "anonymousType");
    org.w3c.dom.Element xmlLeafTypeNode = (org.w3c.dom.Element) xmlLeafType.getChildNodes().item(0);
    AnonymousUserType anonymousType = (AnonymousUserType) decode(xmlLeafTypeNode, map);
    return AnonymousUserConstructor.get(anonymousType);
  }

  private ClassReflectionProxy decodeDeclaringClass(org.w3c.dom.Element xmlElement) {
    return decodeType(xmlElement, "declaringClass");
  }

  private ClassReflectionProxy[] decodeParameters(org.w3c.dom.Element xmlElement) {
    org.w3c.dom.Element xmlParameters =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(
            xmlElement, "parameters");
    java.util.List<org.w3c.dom.Element> xmlTypes =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getChildElementsByTagName(xmlParameters, "type");
    ClassReflectionProxy[] rv = new ClassReflectionProxy[xmlTypes.size()];
    for (int i = 0; i < rv.length; i++) {
      rv[i] = createClassReflectionProxy(xmlTypes.get(i).getAttribute("name"));
    }
    return rv;
  }

  private FieldReflectionProxy decodeField(org.w3c.dom.Element xmlParent, String nodeName) {
    org.w3c.dom.Element xmlField =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(xmlParent, nodeName);
    ClassReflectionProxy declaringCls = decodeDeclaringClass(xmlField);
    String name = xmlField.getAttribute("name");
    return new FieldReflectionProxy(declaringCls, name);
  }

  private ConstructorReflectionProxy decodeConstructor(
      org.w3c.dom.Element xmlParent, String nodeName) {
    org.w3c.dom.Element xmlConstructor =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(xmlParent, nodeName);
    ClassReflectionProxy declaringCls = decodeDeclaringClass(xmlConstructor);
    ClassReflectionProxy[] parameterClses = decodeParameters(xmlConstructor);
    boolean isVarArgs = Boolean.parseBoolean(xmlConstructor.getAttribute("isVarArgs"));
    return new ConstructorReflectionProxy(declaringCls, parameterClses, isVarArgs);
  }

  private MethodReflectionProxy decodeMethod(org.w3c.dom.Element xmlParent, String nodeName) {
    org.w3c.dom.Element xmlMethod =
        edu.cmu.cs.dennisc.xml.XMLUtilities.getSingleChildElementByTagName(xmlParent, nodeName);
    ClassReflectionProxy declaringCls = decodeDeclaringClass(xmlMethod);
    String name = xmlMethod.getAttribute("name");

    String potentialReplacement = Decoder.intraClassMethodMap.get(declaringCls, name);
    if (potentialReplacement != null) {
      name = potentialReplacement;
    } else {
      ClassReflectionProxyAndMethodName classReflectionProxyAndMethodName =
          Decoder.betweenClassesMethodMap.get(declaringCls, name);
      if (classReflectionProxyAndMethodName != null) {
        declaringCls = classReflectionProxyAndMethodName.getClassReflectionProxy();
        name = classReflectionProxyAndMethodName.getName();
      }
    }

    //		name = filterMethodNameIfNecessary( declaringCls, name );
    ClassReflectionProxy[] parameterClses = decodeParameters(xmlMethod);
    boolean isVarArgs = Boolean.parseBoolean(xmlMethod.getAttribute("isVarArgs"));
    return new MethodReflectionProxy(declaringCls, name, parameterClses, isVarArgs);
  }

  private static int getUniqueKey(org.w3c.dom.Element xmlElement) {
    return Integer.parseInt(xmlElement.getAttribute(CodecConstants.UNIQUE_KEY_ATTRIBUTE), 16);
  }

  public AbstractNode decode(
      org.w3c.dom.Element xmlElement, java.util.Map<Integer, AbstractDeclaration> map) {
    AbstractNode rv;
    if (xmlElement.hasAttribute(CodecConstants.TYPE_ATTRIBUTE)) {
      String clsName = getClassName(xmlElement);
      if (clsName.equals(JavaType.class.getName())) {
        rv = JavaType.getInstance(decodeType(xmlElement, "type"));
      } else if (clsName.equals(UserArrayType.class.getName())) {
        rv = decodeUserArrayType(xmlElement, map);
      } else if (clsName.equals(JavaConstructor.class.getName())) {
        rv = JavaConstructor.getInstance(decodeConstructor(xmlElement, "constructor"));
      } else if (clsName.equals(JavaMethod.class.getName())) {
        MethodReflectionProxy methodReflectionProxy = decodeMethod(xmlElement, "method");
        MethodReflectionProxy varArgsReplacement =
            MethodReflectionProxy.getReplacementIfNecessary(methodReflectionProxy);
        if (varArgsReplacement != null) {
          edu.cmu.cs.dennisc.java.util.logging.Logger.errln(
              "replacing", methodReflectionProxy, "with", varArgsReplacement);
          methodReflectionProxy = varArgsReplacement;
        }
        rv = JavaMethod.getInstance(methodReflectionProxy);
      } else if (clsName.equals(Getter.class.getName()) || clsName.equals(Setter.class.getName())) {

        org.w3c.dom.Node xmlFirstChild = xmlElement.getFirstChild();
        if (xmlFirstChild instanceof org.w3c.dom.Element) {
          org.w3c.dom.Element xmlFirstChildElement = (org.w3c.dom.Element) xmlFirstChild;
          if (xmlFirstChildElement.hasAttribute(CodecConstants.UNIQUE_KEY_ATTRIBUTE)) {
            int getterOrSetterUniqueKey = getUniqueKey(xmlElement);
            int fieldUniqueKey = getUniqueKey(xmlFirstChildElement);
            java.util.Map<Integer, Integer> mapToFieldKey;
            if (clsName.equals(Getter.class.getName())) {
              mapToFieldKey = EPIC_HACK_mapGetterKeyToFieldKey;
            } else {
              mapToFieldKey = EPIC_HACK_mapSetterKeyToFieldKey;
            }
            mapToFieldKey.put(getterOrSetterUniqueKey, fieldUniqueKey);
          }
        }

        org.w3c.dom.NodeList nodeList = xmlElement.getChildNodes();
        assert nodeList.getLength() == 1;
        org.w3c.dom.Element xmlField = (org.w3c.dom.Element) nodeList.item(0);
        UserField field = (UserField) decode(xmlField, map);
        if (clsName.equals(Getter.class.getName())) {
          rv = field.getGetter();
        } else {
          rv = field.getSetter();
        }
      } else if (clsName.equals(SetterParameter.class.getName())) {
        org.w3c.dom.NodeList nodeList = xmlElement.getChildNodes();
        assert nodeList.getLength() == 1;
        org.w3c.dom.Element xmlSetter = (org.w3c.dom.Element) nodeList.item(0);
        Setter setter = (Setter) decode(xmlSetter, map);
        rv = setter.getRequiredParameters().get(0);
      } else if (clsName.equals(JavaField.class.getName())) {
        rv = JavaField.getInstance(decodeField(xmlElement, "field"));
      } else if (clsName.equals(AnonymousUserConstructor.class.getName())) {
        rv = decodeAnonymousConstructor(xmlElement, map);
      } else if (clsName.equals(JavaConstructorParameter.class.getName())) {
        org.w3c.dom.NodeList nodeList = xmlElement.getChildNodes();
        assert nodeList.getLength() == 2;
        org.w3c.dom.Element xmlConstructor = (org.w3c.dom.Element) nodeList.item(0);
        JavaConstructor constructorDeclaredInJava =
            (JavaConstructor) decodeValue(xmlConstructor, map);
        org.w3c.dom.Element xmlIndex = (org.w3c.dom.Element) nodeList.item(1);
        int index = Integer.parseInt(xmlIndex.getTextContent());

        final int REQUIRED_N = constructorDeclaredInJava.getRequiredParameters().size();
        if (index < REQUIRED_N) {
          rv = constructorDeclaredInJava.getRequiredParameters().get(index);
        } else {
          if (index == REQUIRED_N) {
            rv = constructorDeclaredInJava.getVariableLengthParameter();
            if (rv != null) {
              // pass;
            } else {
              rv = constructorDeclaredInJava.getKeyedParameter();
            }
          } else {
            rv = null;
          }
        }
      } else if (clsName.equals(JavaMethodParameter.class.getName())) {
        org.w3c.dom.NodeList nodeList = xmlElement.getChildNodes();
        assert nodeList.getLength() == 2;
        org.w3c.dom.Element xmlMethod = (org.w3c.dom.Element) nodeList.item(0);
        JavaMethod methodDeclaredInJava = (JavaMethod) decodeValue(xmlMethod, map);
        org.w3c.dom.Element xmlIndex = (org.w3c.dom.Element) nodeList.item(1);
        int index = Integer.parseInt(xmlIndex.getTextContent());
        final int REQUIRED_N = methodDeclaredInJava.getRequiredParameters().size();
        if (index < REQUIRED_N) {
          rv = methodDeclaredInJava.getRequiredParameters().get(index);
        } else {
          if (index == REQUIRED_N) {
            rv = methodDeclaredInJava.getVariableLengthParameter();
            if (rv != null) {
              // pass;
            } else {
              rv = methodDeclaredInJava.getKeyedParameter();
            }
          } else {
            rv = null;
          }
        }
      } else {
        rv = (AbstractNode) newInstance(xmlElement);
        assert rv != null;
      }
      if (rv instanceof AbstractDeclaration) {
        map.put(getUniqueKey(xmlElement), (AbstractDeclaration) rv);
      }
      rv.decodeNode(this, xmlElement, map);
      if (xmlElement.hasAttribute(CodecConstants.ID_ATTRIBUTE)) {
        if (this.policy.isIdPreserved()) {
          rv.setId(java.util.UUID.fromString(xmlElement.getAttribute(CodecConstants.ID_ATTRIBUTE)));
        }
      }
    } else {
      int key = getUniqueKey(xmlElement);
      rv = map.get(key);
      if (rv != null) {
        // pass
      } else {
        if (EPIC_HACK_mapArrayTypeKeyToLeafTypeKey.containsKey(key)) {
          int leafTypeKey = EPIC_HACK_mapArrayTypeKeyToLeafTypeKey.get(key);
          AbstractDeclaration leafDeclaration = map.get(leafTypeKey);
          if (leafDeclaration instanceof UserType<?>) {
            UserType<?> leafType = (UserType<?>) leafDeclaration;
            edu.cmu.cs.dennisc.java.util.logging.Logger.outln(leafTypeKey, leafType);
            rv = leafType.getArrayType();
          } else {
            assert false : leafDeclaration;
          }
        } else if (EPIC_HACK_mapGetterKeyToFieldKey.containsKey(key)) {
          int fieldKey = EPIC_HACK_mapGetterKeyToFieldKey.get(key);
          AbstractDeclaration fieldDeclaration = map.get(fieldKey);
          if (fieldDeclaration instanceof UserField) {
            UserField userField = (UserField) fieldDeclaration;
            rv = userField.getGetter();
          }
        } else if (EPIC_HACK_mapSetterKeyToFieldKey.containsKey(key)) {
          int fieldKey = EPIC_HACK_mapSetterKeyToFieldKey.get(key);
          AbstractDeclaration fieldDeclaration = map.get(fieldKey);
          if (fieldDeclaration instanceof UserField) {
            UserField userField = (UserField) fieldDeclaration;
            rv = userField.getSetter();
          }
        } else {
          assert false : Integer.toHexString(key) + " " + map;
        }
      }
    }
    return rv;
  }

  private final java.util.Map<Integer, Integer> EPIC_HACK_mapArrayTypeKeyToLeafTypeKey =
      edu.cmu.cs.dennisc.java.util.Maps.newHashMap();
  private final java.util.Map<Integer, Integer> EPIC_HACK_mapGetterKeyToFieldKey =
      edu.cmu.cs.dennisc.java.util.Maps.newHashMap();
  private final java.util.Map<Integer, Integer> EPIC_HACK_mapSetterKeyToFieldKey =
      edu.cmu.cs.dennisc.java.util.Maps.newHashMap();

  private final org.lgna.project.Version srcVersion;
  private final org.lgna.project.Version dstVersion;
  private final DecodeIdPolicy policy;
}