org.chromium.sdk/src/org/chromium/sdk/internal/ContextBuilder.java
changeset 2 e4420d2515f1
child 52 f577ea64429e
equal deleted inserted replaced
1:ef76fc2ac88c 2:e4420d2515f1
       
     1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style license that can be
       
     3 // found in the LICENSE file.
       
     4 
       
     5 package org.chromium.sdk.internal;
       
     6 
       
     7 import java.util.ArrayList;
       
     8 import java.util.Collection;
       
     9 import java.util.Collections;
       
    10 import java.util.List;
       
    11 
       
    12 import org.chromium.sdk.Breakpoint;
       
    13 import org.chromium.sdk.CallFrame;
       
    14 import org.chromium.sdk.DebugContext;
       
    15 import org.chromium.sdk.ExceptionData;
       
    16 import org.chromium.sdk.Script;
       
    17 import org.chromium.sdk.SyncCallback;
       
    18 import org.chromium.sdk.internal.protocol.CommandResponse;
       
    19 import org.chromium.sdk.internal.protocol.SuccessCommandResponse;
       
    20 import org.chromium.sdk.internal.tools.v8.V8CommandProcessor;
       
    21 import org.chromium.sdk.internal.tools.v8.V8CommandProcessor.V8HandlerCallback;
       
    22 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessage;
       
    23 import org.chromium.sdk.internal.tools.v8.request.DebuggerMessageFactory;
       
    24 
       
    25 public class ContextBuilder {
       
    26   private final DebugSession debugSession;
       
    27 
       
    28   /**
       
    29    * Context building process comes though sequence of steps. No one should
       
    30    * be able to work with any step exception the current one.
       
    31    */
       
    32   private Object currentStep = null;
       
    33 
       
    34   ContextBuilder(DebugSession debugSession) {
       
    35     this.debugSession = debugSession;
       
    36   }
       
    37 
       
    38   public interface ExpectingBreakEventStep {
       
    39     InternalContext getInternalContext();
       
    40     /**
       
    41      * Stores the breakpoints associated with V8 suspension event (empty if an
       
    42      * exception or a step end).
       
    43      *
       
    44      * @param breakpointsHit the breakpoints that were hit
       
    45      */
       
    46     ExpectingBacktraceStep setContextState(Collection<Breakpoint> breakpointsHit,
       
    47         ExceptionData exceptionData);
       
    48   }
       
    49   public interface ExpectingBacktraceStep {
       
    50     InternalContext getInternalContext();
       
    51 
       
    52     DebugContext setFrames(FrameMirror[] frameMirrors);
       
    53   }
       
    54 
       
    55   /**
       
    56    * Starting point of building new DebugContext process. One should traverse
       
    57    * list of steps to get the result.
       
    58    * @return object representing first step of context building process
       
    59    */
       
    60   public ExpectingBreakEventStep buildNewContext() {
       
    61     assertStep(null);
       
    62 
       
    63     final PreContext preContext = new PreContext();
       
    64     final DebugContextData contextData = new DebugContextData();
       
    65 
       
    66     return new ExpectingBreakEventStep() {
       
    67       {
       
    68         currentStep = this;
       
    69       }
       
    70 
       
    71       public InternalContext getInternalContext() {
       
    72         return preContext;
       
    73       }
       
    74 
       
    75       public ExpectingBacktraceStep setContextState(Collection<Breakpoint> breakpointsHit,
       
    76           ExceptionData exceptionData) {
       
    77         assertStep(this);
       
    78 
       
    79         DebugContext.State state;
       
    80         if (exceptionData == null) {
       
    81           state = DebugContext.State.NORMAL;
       
    82         } else {
       
    83           state = DebugContext.State.EXCEPTION;
       
    84         }
       
    85 
       
    86         contextData.contextState = state;
       
    87         contextData.breakpointsHit = Collections.unmodifiableCollection(breakpointsHit);
       
    88         contextData.exceptionData = exceptionData;
       
    89 
       
    90         return new ExpectingBacktraceStep() {
       
    91           {
       
    92             currentStep = this;
       
    93           }
       
    94 
       
    95           public InternalContext getInternalContext() {
       
    96             return preContext;
       
    97           }
       
    98 
       
    99           public DebugContext setFrames(FrameMirror[] frameMirrors) {
       
   100             assertStep(this);
       
   101 
       
   102             contextData.frames = new Frames(frameMirrors, preContext);
       
   103 
       
   104             preContext.createContext(contextData);
       
   105 
       
   106             DebugContext userContext = preContext.getContext();
       
   107             currentStep = userContext;
       
   108             return userContext;
       
   109           }
       
   110         };
       
   111       }
       
   112     };
       
   113   }
       
   114 
       
   115   public ExpectingBreakEventStep buildNewContextWhenIdle() {
       
   116     if (currentStep == null) {
       
   117       return buildNewContext();
       
   118     } else {
       
   119       return null;
       
   120     }
       
   121   }
       
   122 
       
   123   /**
       
   124    * Debug session is stopped. Cancel context in any state.
       
   125    */
       
   126   public void forceCancelContext() {
       
   127     // TODO(peter.rybin): complete it
       
   128   }
       
   129 
       
   130   public void buildSequenceFailure() {
       
   131     // this means we can't go on debugging
       
   132     // TODO(peter.rybin): implement
       
   133     throw new RuntimeException();
       
   134   }
       
   135 
       
   136   private void contextDismissed(DebugContext userContext) {
       
   137     assertStep(userContext);
       
   138     currentStep = null;
       
   139   }
       
   140 
       
   141   private void assertStep(Object step) {
       
   142     if (currentStep != step) {
       
   143       throw new IllegalStateException("Expected " + step + ", but was " + currentStep);
       
   144     }
       
   145   }
       
   146 
       
   147   private class PreContext implements InternalContext {
       
   148     private final HandleManager handleManager = new HandleManager();
       
   149     private final ValueLoader valueLoader = new ValueLoader(this);
       
   150 
       
   151     /**
       
   152      * We synchronize {@link #isValid} state with commands that are being sent
       
   153      * using this monitor.
       
   154      */
       
   155     private final Object sendContextCommandsMonitor = new Object();
       
   156     private volatile boolean isValid = true;
       
   157     private UserContext context = null;
       
   158 
       
   159     public boolean isValid() {
       
   160       return isValid;
       
   161     }
       
   162 
       
   163     public DebugSession getDebugSession() {
       
   164       return debugSession;
       
   165     }
       
   166 
       
   167     public ContextBuilder getContextBuilder() {
       
   168       return ContextBuilder.this;
       
   169     }
       
   170 
       
   171     void assertValid() {
       
   172       if (!isValid) {
       
   173         throw new IllegalStateException("This instance of DebugContext cannot be used anymore");
       
   174       }
       
   175     }
       
   176 
       
   177     /**
       
   178      * Check if context is valid right now. Throws exception if we are in a strict mode.
       
   179      * Ignores otherwise.
       
   180      */
       
   181     void assertValidForUser() {
       
   182       if (!isValid) {
       
   183         debugSession.maybeRethrowContextException(null);
       
   184       }
       
   185     }
       
   186 
       
   187     public UserContext getContext() {
       
   188       if (context == null) {
       
   189         throw new IllegalStateException();
       
   190       }
       
   191       return context;
       
   192     }
       
   193 
       
   194     public CallFrameImpl getTopFrameImpl() {
       
   195       assertValid();
       
   196       return getContext().data.frames.getCallFrames().get(0);
       
   197     }
       
   198 
       
   199     public HandleManager getHandleManager() {
       
   200       // tolerates dismissed context
       
   201       return handleManager;
       
   202     }
       
   203 
       
   204     public ValueLoader getValueLoader() {
       
   205       return valueLoader;
       
   206     }
       
   207 
       
   208 
       
   209     void createContext(DebugContextData contextData) {
       
   210       if (context != null) {
       
   211         throw new IllegalStateException();
       
   212       }
       
   213       context = new UserContext(contextData);
       
   214     }
       
   215 
       
   216     public void sendV8CommandAsync(DebuggerMessage message, boolean isImmediate,
       
   217         V8HandlerCallback commandCallback, SyncCallback syncCallback)
       
   218         throws ContextDismissedCheckedException {
       
   219       synchronized (sendContextCommandsMonitor) {
       
   220         if (!isValid) {
       
   221           throw new ContextDismissedCheckedException();
       
   222         }
       
   223         debugSession.getV8CommandProcessor().sendV8CommandAsync(message, isImmediate,
       
   224             commandCallback, syncCallback);
       
   225       }
       
   226     }
       
   227 
       
   228     private void sendMessageAsyncAndIvalidate(DebuggerMessage message,
       
   229         V8CommandProcessor.V8HandlerCallback commandCallback, boolean isImmediate,
       
   230         SyncCallback syncCallback) {
       
   231       synchronized (sendContextCommandsMonitor) {
       
   232         assertValid();
       
   233         debugSession.getV8CommandProcessor().sendV8CommandAsync(message, isImmediate,
       
   234             commandCallback, syncCallback);
       
   235         isValid = false;
       
   236       }
       
   237     }
       
   238 
       
   239     private class UserContext implements DebugContext {
       
   240       private final DebugContextData data;
       
   241 
       
   242       public UserContext(DebugContextData contextData) {
       
   243         this.data = contextData;
       
   244       }
       
   245 
       
   246       public State getState() {
       
   247         assertValidForUser();
       
   248         return data.contextState;
       
   249       }
       
   250       public List<? extends CallFrame> getCallFrames() {
       
   251         assertValidForUser();
       
   252         return data.frames.getCallFrames();
       
   253       }
       
   254 
       
   255       public Collection<Breakpoint> getBreakpointsHit() {
       
   256         assertValidForUser();
       
   257         if (data.breakpointsHit == null) {
       
   258           throw new RuntimeException();
       
   259         }
       
   260         return data.breakpointsHit;
       
   261       }
       
   262 
       
   263       public ExceptionData getExceptionData() {
       
   264         assertValidForUser();
       
   265         return data.exceptionData;
       
   266       }
       
   267 
       
   268       /**
       
   269        * @throws IllegalStateException if context has already been continued
       
   270        */
       
   271       public void continueVm(StepAction stepAction, int stepCount,
       
   272           final ContinueCallback callback) {
       
   273         if (stepAction == null) {
       
   274           throw new NullPointerException();
       
   275         }
       
   276 
       
   277         DebuggerMessage message = DebuggerMessageFactory.goOn(stepAction, stepCount);
       
   278         V8CommandProcessor.V8HandlerCallback commandCallback
       
   279             = new V8CommandProcessor.V8HandlerCallback() {
       
   280           public void messageReceived(CommandResponse response) {
       
   281             SuccessCommandResponse successResponse = response.asSuccess();
       
   282             if (successResponse == null) {
       
   283               this.failure(response.asFailure().getMessage());
       
   284               return;
       
   285             }
       
   286 
       
   287             contextDismissed(UserContext.this);
       
   288 
       
   289             if (callback != null) {
       
   290               callback.success();
       
   291             }
       
   292             getDebugSession().getDebugEventListener().resumed();
       
   293           }
       
   294           public void failure(String message) {
       
   295             synchronized (sendContextCommandsMonitor) {
       
   296               // resurrected
       
   297               isValid = true;
       
   298             }
       
   299             if (callback != null) {
       
   300               callback.failure(message);
       
   301             }
       
   302           }
       
   303         };
       
   304 
       
   305         sendMessageAsyncAndIvalidate(message, commandCallback, true, null);
       
   306       }
       
   307 
       
   308       InternalContext getInternalContextForTests() {
       
   309         return PreContext.this;
       
   310       }
       
   311     }
       
   312   }
       
   313 
       
   314   /**
       
   315    * Simple structure of data which DebugConext implementation uses.
       
   316    */
       
   317   private static class DebugContextData {
       
   318     private Frames frames;
       
   319     /** The breakpoints hit before suspending. */
       
   320     private volatile Collection<Breakpoint> breakpointsHit;
       
   321 
       
   322     DebugContext.State contextState;
       
   323     /** The JavaScript exception state. */
       
   324     private ExceptionData exceptionData;
       
   325   }
       
   326 
       
   327   private class Frames {
       
   328     /** The frame mirrors while on a breakpoint. */
       
   329     private final FrameMirror[] frameMirrors;
       
   330     /** The cached call frames constructed using frameMirrors. */
       
   331     private final List<CallFrameImpl> unmodifableFrames;
       
   332     private boolean scriptsLinkedToFrames;
       
   333 
       
   334     Frames(FrameMirror[] frameMirrors0, InternalContext internalContext) {
       
   335       this.frameMirrors = frameMirrors0;
       
   336       this.scriptsLinkedToFrames = false;
       
   337 
       
   338       int frameCount = frameMirrors.length;
       
   339       List<CallFrameImpl> frameList = new ArrayList<CallFrameImpl>(frameCount);
       
   340       for (int i = 0; i < frameCount; ++i) {
       
   341         frameList.add(new CallFrameImpl(frameMirrors[i], i, internalContext));
       
   342       }
       
   343       this.unmodifableFrames = Collections.unmodifiableList(frameList);
       
   344     }
       
   345 
       
   346     synchronized List<CallFrameImpl> getCallFrames() {
       
   347       if (!scriptsLinkedToFrames) {
       
   348         // We expect that ALL the V8 scripts are loaded so we can
       
   349         // hook them up to the call frames.
       
   350         int frameCount = frameMirrors.length;
       
   351         for (int i = 0; i < frameCount; ++i) {
       
   352           hookupScriptToFrame(i);
       
   353         }
       
   354         scriptsLinkedToFrames = true;
       
   355       }
       
   356       return unmodifableFrames;
       
   357     }
       
   358 
       
   359 
       
   360     /**
       
   361      * Associates a script found in the ScriptManager with the given frame.
       
   362      *
       
   363      * @param frameIndex to associate a script with
       
   364      */
       
   365     private void hookupScriptToFrame(int frameIndex) {
       
   366       FrameMirror frame = frameMirrors[frameIndex];
       
   367       if (frame != null && frame.getScript() == null) {
       
   368         Script script = debugSession.getScriptManager().findById(frame.getScriptId());
       
   369         if (script != null) {
       
   370           frame.setScript(script);
       
   371         }
       
   372       }
       
   373     }
       
   374   }
       
   375 
       
   376   static InternalContext getInternalContextForTests(DebugContext debugContext) {
       
   377     PreContext.UserContext userContext = (PreContext.UserContext) debugContext;
       
   378     return userContext.getInternalContextForTests();
       
   379   }
       
   380 }