org.chromium.sdk/src/org/chromium/sdk/internal/ContextBuilder.java
changeset 2 e4420d2515f1
child 52 f577ea64429e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.sdk/src/org/chromium/sdk/internal/ContextBuilder.java	Wed Dec 23 17:13:18 2009 -0800
@@ -0,0 +1,380 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.sdk.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.CallFrame;
+import org.chromium.sdk.DebugContext;
+import org.chromium.sdk.ExceptionData;
+import org.chromium.sdk.Script;
+import org.chromium.sdk.SyncCallback;
+import org.chromium.sdk.internal.protocol.CommandResponse;
+import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
+import org.chromium.sdk.internal.tools.v8.V8CommandProcessor;
+import org.chromium.sdk.internal.tools.v8.V8CommandProcessor.V8HandlerCallback;
+import org.chromium.sdk.internal.tools.v8.request.DebuggerMessage;
+import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
+
+public class ContextBuilder {
+  private final DebugSession debugSession;
+
+  /**
+   * Context building process comes though sequence of steps. No one should
+   * be able to work with any step exception the current one.
+   */
+  private Object currentStep = null;
+
+  ContextBuilder(DebugSession debugSession) {
+    this.debugSession = debugSession;
+  }
+
+  public interface ExpectingBreakEventStep {
+    InternalContext getInternalContext();
+    /**
+     * Stores the breakpoints associated with V8 suspension event (empty if an
+     * exception or a step end).
+     *
+     * @param breakpointsHit the breakpoints that were hit
+     */
+    ExpectingBacktraceStep setContextState(Collection<Breakpoint> breakpointsHit,
+        ExceptionData exceptionData);
+  }
+  public interface ExpectingBacktraceStep {
+    InternalContext getInternalContext();
+
+    DebugContext setFrames(FrameMirror[] frameMirrors);
+  }
+
+  /**
+   * Starting point of building new DebugContext process. One should traverse
+   * list of steps to get the result.
+   * @return object representing first step of context building process
+   */
+  public ExpectingBreakEventStep buildNewContext() {
+    assertStep(null);
+
+    final PreContext preContext = new PreContext();
+    final DebugContextData contextData = new DebugContextData();
+
+    return new ExpectingBreakEventStep() {
+      {
+        currentStep = this;
+      }
+
+      public InternalContext getInternalContext() {
+        return preContext;
+      }
+
+      public ExpectingBacktraceStep setContextState(Collection<Breakpoint> breakpointsHit,
+          ExceptionData exceptionData) {
+        assertStep(this);
+
+        DebugContext.State state;
+        if (exceptionData == null) {
+          state = DebugContext.State.NORMAL;
+        } else {
+          state = DebugContext.State.EXCEPTION;
+        }
+
+        contextData.contextState = state;
+        contextData.breakpointsHit = Collections.unmodifiableCollection(breakpointsHit);
+        contextData.exceptionData = exceptionData;
+
+        return new ExpectingBacktraceStep() {
+          {
+            currentStep = this;
+          }
+
+          public InternalContext getInternalContext() {
+            return preContext;
+          }
+
+          public DebugContext setFrames(FrameMirror[] frameMirrors) {
+            assertStep(this);
+
+            contextData.frames = new Frames(frameMirrors, preContext);
+
+            preContext.createContext(contextData);
+
+            DebugContext userContext = preContext.getContext();
+            currentStep = userContext;
+            return userContext;
+          }
+        };
+      }
+    };
+  }
+
+  public ExpectingBreakEventStep buildNewContextWhenIdle() {
+    if (currentStep == null) {
+      return buildNewContext();
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Debug session is stopped. Cancel context in any state.
+   */
+  public void forceCancelContext() {
+    // TODO(peter.rybin): complete it
+  }
+
+  public void buildSequenceFailure() {
+    // this means we can't go on debugging
+    // TODO(peter.rybin): implement
+    throw new RuntimeException();
+  }
+
+  private void contextDismissed(DebugContext userContext) {
+    assertStep(userContext);
+    currentStep = null;
+  }
+
+  private void assertStep(Object step) {
+    if (currentStep != step) {
+      throw new IllegalStateException("Expected " + step + ", but was " + currentStep);
+    }
+  }
+
+  private class PreContext implements InternalContext {
+    private final HandleManager handleManager = new HandleManager();
+    private final ValueLoader valueLoader = new ValueLoader(this);
+
+    /**
+     * We synchronize {@link #isValid} state with commands that are being sent
+     * using this monitor.
+     */
+    private final Object sendContextCommandsMonitor = new Object();
+    private volatile boolean isValid = true;
+    private UserContext context = null;
+
+    public boolean isValid() {
+      return isValid;
+    }
+
+    public DebugSession getDebugSession() {
+      return debugSession;
+    }
+
+    public ContextBuilder getContextBuilder() {
+      return ContextBuilder.this;
+    }
+
+    void assertValid() {
+      if (!isValid) {
+        throw new IllegalStateException("This instance of DebugContext cannot be used anymore");
+      }
+    }
+
+    /**
+     * Check if context is valid right now. Throws exception if we are in a strict mode.
+     * Ignores otherwise.
+     */
+    void assertValidForUser() {
+      if (!isValid) {
+        debugSession.maybeRethrowContextException(null);
+      }
+    }
+
+    public UserContext getContext() {
+      if (context == null) {
+        throw new IllegalStateException();
+      }
+      return context;
+    }
+
+    public CallFrameImpl getTopFrameImpl() {
+      assertValid();
+      return getContext().data.frames.getCallFrames().get(0);
+    }
+
+    public HandleManager getHandleManager() {
+      // tolerates dismissed context
+      return handleManager;
+    }
+
+    public ValueLoader getValueLoader() {
+      return valueLoader;
+    }
+
+
+    void createContext(DebugContextData contextData) {
+      if (context != null) {
+        throw new IllegalStateException();
+      }
+      context = new UserContext(contextData);
+    }
+
+    public void sendV8CommandAsync(DebuggerMessage message, boolean isImmediate,
+        V8HandlerCallback commandCallback, SyncCallback syncCallback)
+        throws ContextDismissedCheckedException {
+      synchronized (sendContextCommandsMonitor) {
+        if (!isValid) {
+          throw new ContextDismissedCheckedException();
+        }
+        debugSession.getV8CommandProcessor().sendV8CommandAsync(message, isImmediate,
+            commandCallback, syncCallback);
+      }
+    }
+
+    private void sendMessageAsyncAndIvalidate(DebuggerMessage message,
+        V8CommandProcessor.V8HandlerCallback commandCallback, boolean isImmediate,
+        SyncCallback syncCallback) {
+      synchronized (sendContextCommandsMonitor) {
+        assertValid();
+        debugSession.getV8CommandProcessor().sendV8CommandAsync(message, isImmediate,
+            commandCallback, syncCallback);
+        isValid = false;
+      }
+    }
+
+    private class UserContext implements DebugContext {
+      private final DebugContextData data;
+
+      public UserContext(DebugContextData contextData) {
+        this.data = contextData;
+      }
+
+      public State getState() {
+        assertValidForUser();
+        return data.contextState;
+      }
+      public List<? extends CallFrame> getCallFrames() {
+        assertValidForUser();
+        return data.frames.getCallFrames();
+      }
+
+      public Collection<Breakpoint> getBreakpointsHit() {
+        assertValidForUser();
+        if (data.breakpointsHit == null) {
+          throw new RuntimeException();
+        }
+        return data.breakpointsHit;
+      }
+
+      public ExceptionData getExceptionData() {
+        assertValidForUser();
+        return data.exceptionData;
+      }
+
+      /**
+       * @throws IllegalStateException if context has already been continued
+       */
+      public void continueVm(StepAction stepAction, int stepCount,
+          final ContinueCallback callback) {
+        if (stepAction == null) {
+          throw new NullPointerException();
+        }
+
+        DebuggerMessage message = DebuggerMessageFactory.goOn(stepAction, stepCount);
+        V8CommandProcessor.V8HandlerCallback commandCallback
+            = new V8CommandProcessor.V8HandlerCallback() {
+          public void messageReceived(CommandResponse response) {
+            SuccessCommandResponse successResponse = response.asSuccess();
+            if (successResponse == null) {
+              this.failure(response.asFailure().getMessage());
+              return;
+            }
+
+            contextDismissed(UserContext.this);
+
+            if (callback != null) {
+              callback.success();
+            }
+            getDebugSession().getDebugEventListener().resumed();
+          }
+          public void failure(String message) {
+            synchronized (sendContextCommandsMonitor) {
+              // resurrected
+              isValid = true;
+            }
+            if (callback != null) {
+              callback.failure(message);
+            }
+          }
+        };
+
+        sendMessageAsyncAndIvalidate(message, commandCallback, true, null);
+      }
+
+      InternalContext getInternalContextForTests() {
+        return PreContext.this;
+      }
+    }
+  }
+
+  /**
+   * Simple structure of data which DebugConext implementation uses.
+   */
+  private static class DebugContextData {
+    private Frames frames;
+    /** The breakpoints hit before suspending. */
+    private volatile Collection<Breakpoint> breakpointsHit;
+
+    DebugContext.State contextState;
+    /** The JavaScript exception state. */
+    private ExceptionData exceptionData;
+  }
+
+  private class Frames {
+    /** The frame mirrors while on a breakpoint. */
+    private final FrameMirror[] frameMirrors;
+    /** The cached call frames constructed using frameMirrors. */
+    private final List<CallFrameImpl> unmodifableFrames;
+    private boolean scriptsLinkedToFrames;
+
+    Frames(FrameMirror[] frameMirrors0, InternalContext internalContext) {
+      this.frameMirrors = frameMirrors0;
+      this.scriptsLinkedToFrames = false;
+
+      int frameCount = frameMirrors.length;
+      List<CallFrameImpl> frameList = new ArrayList<CallFrameImpl>(frameCount);
+      for (int i = 0; i < frameCount; ++i) {
+        frameList.add(new CallFrameImpl(frameMirrors[i], i, internalContext));
+      }
+      this.unmodifableFrames = Collections.unmodifiableList(frameList);
+    }
+
+    synchronized List<CallFrameImpl> getCallFrames() {
+      if (!scriptsLinkedToFrames) {
+        // We expect that ALL the V8 scripts are loaded so we can
+        // hook them up to the call frames.
+        int frameCount = frameMirrors.length;
+        for (int i = 0; i < frameCount; ++i) {
+          hookupScriptToFrame(i);
+        }
+        scriptsLinkedToFrames = true;
+      }
+      return unmodifableFrames;
+    }
+
+
+    /**
+     * Associates a script found in the ScriptManager with the given frame.
+     *
+     * @param frameIndex to associate a script with
+     */
+    private void hookupScriptToFrame(int frameIndex) {
+      FrameMirror frame = frameMirrors[frameIndex];
+      if (frame != null && frame.getScript() == null) {
+        Script script = debugSession.getScriptManager().findById(frame.getScriptId());
+        if (script != null) {
+          frame.setScript(script);
+        }
+      }
+    }
+  }
+
+  static InternalContext getInternalContextForTests(DebugContext debugContext) {
+    PreContext.UserContext userContext = (PreContext.UserContext) debugContext;
+    return userContext.getInternalContextForTests();
+  }
+}