Author: [email protected]
Date: Thu Jul 16 16:35:08 2009
New Revision: 5749

Added:
     
trunk/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java    
(contents, props changed)
Modified:
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
    trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
    trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
    trunk/user/test/com/google/gwt/core/CoreSuite.java

Log:
Adds a test for AsyncFragmentLoader.

Review by: bobv


Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java     
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java    Thu  
Jul 16 16:35:08 2009
@@ -24,11 +24,13 @@
  import com.google.gwt.dev.jjs.SourceInfo;
  import com.google.gwt.dev.jjs.ast.Context;
  import com.google.gwt.dev.jjs.ast.HasEnclosingType;
+import com.google.gwt.dev.jjs.ast.JArrayType;
  import com.google.gwt.dev.jjs.ast.JClassLiteral;
  import com.google.gwt.dev.jjs.ast.JDeclaredType;
  import com.google.gwt.dev.jjs.ast.JExpression;
  import com.google.gwt.dev.jjs.ast.JField;
  import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
  import com.google.gwt.dev.jjs.ast.JNewArray;
  import com.google.gwt.dev.jjs.ast.JNode;
  import com.google.gwt.dev.jjs.ast.JPrimitiveType;
@@ -468,9 +470,24 @@
      return (value == null) ? 0 : value;
    }

+  /**
+   * Installs the initial load sequence into the
+   * AsyncFragmentLoader.BROWSER_LOADER. The initializer looks like this:
+   *
+   * <pre>
+       public static AsyncFragmentLoader BROWSER_LOADER = new  
AsyncFragmentLoader(1,
+         new int[] {}, new StandardLoadingStrategy(), new  
StandardLogger());
+     </pre>
+   *
+   * The second argument (<code>new int[]</code>) gets replaced by an array
+   * corresponding to <code>initialLoadSequence</code>.
+   */
    private static void installInitialLoadSequenceField(JProgram program,
        LinkedHashSet<Integer> initialLoadSequence) {
-    JField initLoadSeqField =  
program.getIndexedField("AsyncFragmentLoader.initialLoadSequence");
+    JMethodCall constructorCall =  
ReplaceRunAsyncs.getBrowserLoaderConstructor(program);
+    assert constructorCall.getArgs().get(1).getType() instanceof  
JArrayType;
+    assert ((JArrayType)  
constructorCall.getArgs().get(1).getType()).getElementType() ==  
JPrimitiveType.INT;
+
      SourceInfo info =  
program.createSourceInfoSynthetic(ReplaceRunAsyncs.class,
          "array with initial load sequence");
      List<JExpression> intExprs = new ArrayList<JExpression>();
@@ -481,8 +498,8 @@
       * Note: the following field is known to have a manually installed
       * initializer, of new int[0].
       */
-    initLoadSeqField.getDeclarationStatement().initializer =  
JNewArray.createInitializers(
-        program, info, program.getTypeArray(JPrimitiveType.INT, 1),  
intExprs);
+    constructorCall.setArg(1, JNewArray.createInitializers(program, info,
+        program.getTypeArray(JPrimitiveType.INT, 1), intExprs));
    }

    private static <T> T last(T[] array) {

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java     
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java     
 
Thu Jul 16 16:35:08 2009
@@ -817,7 +817,7 @@

    public void traverseFromLeftoversFragmentHasLoaded() {
      if (program.entryMethods.size() > 1) {
-       
traverseFrom(program.getIndexedMethod("AsyncFragmentLoader.leftoversFragmentHasLoaded"));
+       
traverseFrom(program.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded"));
      }
    }


Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java       
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java       
 
Thu Jul 16 16:35:08 2009
@@ -207,10 +207,11 @@
     * {...@link  
com.google.gwt.core.client.impl.AsyncFragmentLoader#leftoversFragmentHasLoaded()}.
     */
    public List<JsStatement> createCallToLeftoversFragmentHasLoaded() {
-    JMethod loadedMethod =  
jprogram.getIndexedMethod("AsyncFragmentLoader.leftoversFragmentHasLoaded");
+    JMethod loadedMethod =  
jprogram.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded");
      JsName loadedMethodName = map.nameForMethod(loadedMethod);
      SourceInfo sourceInfo = jsprogram.getSourceInfo().makeChild(
-        FragmentExtractor.class, "call to leftoversFragmentHasLoaded ");
+        FragmentExtractor.class,
+        "call to browserLoaderLeftoversFragmentHasLoaded ");
      JsInvocation call = new JsInvocation(sourceInfo);
      call.setQualifier(loadedMethodName.makeRef(sourceInfo));
      List<JsStatement> newStats = Collections.<JsStatement>  
singletonList(call.makeStmt());
@@ -220,9 +221,9 @@
    /**
     * Assume that all code described by <code>alreadyLoadedPredicate</code>  
has
     * been downloaded. Extract enough JavaScript statements that the code
-   * described by <code>livenessPredicate</code> can also run. The caller
-   * should ensure that <code>livenessPredicate</code> includes strictly  
more
-   * live code than <code>alreadyLoadedPredicate</code>.
+   * described by <code>livenessPredicate</code> can also run. The caller  
should
+   * ensure that <code>livenessPredicate</code> includes strictly more  
live code
+   * than <code>alreadyLoadedPredicate</code>.
     */
    public List<JsStatement> extractStatements(
        LivenessPredicate livenessPredicate,
@@ -301,7 +302,7 @@
        entryMethodNames.add(name);
      }

-    JMethod leftoverFragmentLoaded =  
jprogram.getIndexedMethod("AsyncFragmentLoader.leftoversFragmentHasLoaded");
+    JMethod leftoverFragmentLoaded =  
jprogram.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded");
      if (leftoverFragmentLoaded != null) {
        JsName name = map.nameForMethod(leftoverFragmentLoaded);
        assert name != null;

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java       
 
Thu Jul 16 16:35:08 2009
@@ -122,14 +122,14 @@
      srcWriter.println("public static void onLoad() {");
      srcWriter.println("loaded = true;");
      srcWriter.println("instance = new " + getLoaderSimpleName() + "();");
-    srcWriter.println(ASYNC_FRAGMENT_LOADER + ".fragmentHasLoaded("
+    srcWriter.println(ASYNC_FRAGMENT_LOADER  
+ ".BROWSER_LOADER.fragmentHasLoaded("
          + entryNumber + ");");

      srcWriter.println(ASYNC_FRAGMENT_LOADER
-        + ".logEventProgress(\"runCallbacks" + entryNumber + "\",  
\"begin\");");
+        + ".BROWSER_LOADER.logEventProgress(\"runCallbacks" + entryNumber  
+ "\", \"begin\");");
      srcWriter.println("instance.runCallbacks();");
      srcWriter.println(ASYNC_FRAGMENT_LOADER
-        + ".logEventProgress(\"runCallbacks" + entryNumber + "\",  
\"end\");");
+        + ".BROWSER_LOADER.logEventProgress(\"runCallbacks" + entryNumber  
+ "\", \"end\");");

      srcWriter.println("}");
    }
@@ -160,7 +160,7 @@
      srcWriter.println("}");
      srcWriter.println("if (!loading) {");
      srcWriter.println("loading = true;");
-    srcWriter.println("AsyncFragmentLoader.inject(" + entryNumber + ",");
+    srcWriter.println("AsyncFragmentLoader.BROWSER_LOADER.inject(" +  
entryNumber + ",");
      srcWriter.println("  new AsyncFragmentLoader.LoadErrorHandler() {");
      srcWriter.println("    public void loadFailed(Throwable reason) {");
      srcWriter.println("      loading = false;");

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java       
 
Thu Jul 16 16:35:08 2009
@@ -1147,7 +1147,7 @@
        List<JsFunction> nonInitialEntries =  
Arrays.asList(entryFunctions).subList(
            x.getEntryCount(0), entryFunctions.length);
        if (!nonInitialEntries.isEmpty()) {
-        JMethod loadedMethod =  
program.getIndexedMethod("AsyncFragmentLoader.leftoversFragmentHasLoaded");
+        JMethod loadedMethod =  
program.getIndexedMethod("AsyncFragmentLoader.browserLoaderLeftoversFragmentHasLoaded");
          JsName loadedMethodName = names.get(loadedMethod);
          SourceInfo sourceInfo = jsProgram.getSourceInfo().makeChild(
              GenerateJavaScriptAST.class, "call to  
leftoversFragmentHasLoaded ");

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java     
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/JavaToJavaScriptMap.java     
 
Thu Jul 16 16:35:08 2009
@@ -26,17 +26,18 @@
   */
  public interface JavaToJavaScriptMap {
    /**
-   * Return the JavaScript name corresponding to a Java type.
-   */
-  JsName nameForType(JReferenceType type);
-
-  /**
     * Return the JavaScript name corresponding to a Java method.
     */
    JsName nameForMethod(JMethod method);

    /**
-   * If <code>name</code> is the name of a <code>var<code> that  
corresponds to a Java
+   * Return the JavaScript name corresponding to a Java type.
+   */
+  JsName nameForType(JReferenceType type);
+
+  /**
+   * If <code>name</code> is the name of a
+   * <code>var<code> that corresponds to a Java
     * static field, then return that field. Otherwise, return null.
     */
    JField nameToField(JsName name);
@@ -52,16 +53,16 @@
     * string literal, then return the string it interns. Otherwise, return  
null.
     */
    String stringLiteralForName(JsName var);
-
+
    /**
-   * If <code>stat</code> is used to set up the definition of some class,
-   * return that class. Otherwise, return null.
+   * If <code>stat</code> is used to set up the definition of some class,  
return
+   * that class. Otherwise, return null.
     */
    JReferenceType typeForStatement(JsStatement stat);
-
+
    /**
-   * If <code>stat</code> is used to set up a vtable entry for a method,
-   * then return that method.  Otherwise return null.
+   * If <code>stat</code> is used to set up a vtable entry for a method,  
then
+   * return that method. Otherwise return null.
     */
    JMethod vtableInitToMethod(JsStatement stat);
  }

Modified:  
trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java        
 
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java        
 
Thu Jul 16 16:35:08 2009
@@ -23,6 +23,7 @@
  import com.google.gwt.dev.jjs.ast.JMethod;
  import com.google.gwt.dev.jjs.ast.JMethodCall;
  import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
  import com.google.gwt.dev.jjs.ast.JProgram;
  import com.google.gwt.dev.jjs.ast.JType;

@@ -120,6 +121,17 @@
      new ReplaceRunAsyncs(program).execImpl();
    }

+  /**
+   * Extract the initializer of AsyncFragmentLoader.BROWSER_LOADER. A  
couple of
+   * parts of the compiler modify this constructor call.
+   */
+  static JMethodCall getBrowserLoaderConstructor(JProgram program) {
+    JField field =  
program.getIndexedField("AsyncFragmentLoader.BROWSER_LOADER");
+    JMethodCall constructorCall = (JMethodCall)  
field.getDeclarationStatement().getInitializer();
+    assert constructorCall.getArgs().size() == 4;
+    return constructorCall;
+  }
+
    private JProgram program;
    private Map<Integer, RunAsyncReplacement> runAsyncReplacements = new  
HashMap<Integer, RunAsyncReplacement>();

@@ -173,7 +185,8 @@
    }

    private void setNumEntriesInAsyncFragmentLoader(int entryCount) {
-    JField field =  
program.getIndexedField("AsyncFragmentLoader.numEntries");
-    field.getDeclarationStatement().initializer =  
program.getLiteralInt(entryCount);
+    JMethodCall constructorCall = getBrowserLoaderConstructor(program);
+    assert constructorCall.getArgs().get(0).getType() ==  
JPrimitiveType.INT;
+    constructorCall.setArg(0, program.getLiteralInt(entryCount));
    }
  }

Modified:  
trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
==============================================================================
--- trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java     
 
(original)
+++ trunk/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java     
 
Thu Jul 16 16:35:08 2009
@@ -16,7 +16,6 @@
  package com.google.gwt.core.client.impl;

  import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsArrayInteger;
  import com.google.gwt.xhr.client.ReadyStateChangeHandler;
  import com.google.gwt.xhr.client.XMLHttpRequest;

@@ -73,6 +72,26 @@
    }

    /**
+   * A strategy for loading code fragments.
+   */
+  public interface LoadingStrategy {
+    void startLoadingFragment(int fragment, LoadErrorHandler  
loadErrorHandler);
+  }
+
+  /**
+   * A strategy for logging progress.
+   */
+  public interface Logger {
+    /**
+     * Log an event. The <code>fragment</code> and <code>size</code> are  
boxed
+     * so that they can be optional. A value of <code>null</code> for  
either one
+     * means that they are not specified.
+     */
+    void logEventProgress(String eventGroup, String type, Integer fragment,
+        Integer size);
+  }
+
+  /**
     * Labels used for runAsync lightweight metrics.
     */
    public static class LwmLabels {
@@ -88,6 +107,40 @@
    }

    /**
+   * A trivial queue of int's that should compile much better than a
+   * LinkedList&lt;Integer&gt;. It assumes that there will be a maximum  
number
+   * of items passed through the queue for its entire life.
+   */
+  private static class BoundedIntQueue {
+    private final int[] array;
+    private int read = 0;
+    private int write = 0;
+
+    public BoundedIntQueue(int maxPuts) {
+      array = new int[maxPuts];
+    }
+
+    public void add(int x) {
+      assert (write < array.length);
+      array[write++] = x;
+    }
+
+    public int peek() {
+      assert read < write;
+      return array[read];
+    }
+
+    public int remove() {
+      assert read < write;
+      return array[read++];
+    }
+
+    public int size() {
+      return write - read;
+    }
+  }
+
+  /**
     * An exception indicating than at HTTP download failed.
     */
    private static class HttpDownloadFailure extends RuntimeException {
@@ -106,8 +159,7 @@
    /**
     * Handles a failure to download a fragment in the initial sequence.
     */
-  private static class InitialFragmentDownloadFailed implements
-      LoadErrorHandler {
+  private class InitialFragmentDownloadFailed implements LoadErrorHandler {
      public void loadFailed(Throwable reason) {
        initialFragmentsLoading = false;

@@ -120,10 +172,10 @@
        List<LoadErrorHandler> handlersToRun = new  
ArrayList<LoadErrorHandler>();

        // add handlers that are waiting pending the initials download
-      assert waitingForInitialFragments.length() ==  
waitingForInitialFragmentsErrorHandlers.size();
-      while (waitingForInitialFragments.length() > 0) {
+      assert waitingForInitialFragments.size() ==  
waitingForInitialFragmentsErrorHandlers.size();
+      while (waitingForInitialFragments.size() > 0) {
           
handlersToRun.add(waitingForInitialFragmentsErrorHandlers.remove());
-        waitingForInitialFragments.shift();
+        waitingForInitialFragments.remove();
        }

        // add handlers for pending initial fragment downloads
@@ -150,6 +202,122 @@
      }
    }

+  /**
+   * The standard logger used in a web browser. It uses the lightweight  
metrics
+   * system.
+   */
+  private static class StandardLogger implements Logger {
+    /**
+     * Always use this as {...@link isStatsAvailable} &amp;&amp;
+     * {...@link #stats(JavaScriptObject)}.
+     */
+    private static native boolean stats(JavaScriptObject data) /*-{
+      return $stats(data);
+    }-*/;
+
+    public void logEventProgress(String eventGroup, String type,
+        Integer fragment, Integer size) {
+      @SuppressWarnings("unused")
+      boolean toss = isStatsAvailable()
+          && stats(createStatsEvent(eventGroup, type, fragment, size));
+    }
+
+    private native JavaScriptObject createStatsEvent(String eventGroup,
+        String type, Integer fragment, Integer size) /*-{
+      var evt = {
+       moduleName: @com.google.gwt.core.client.GWT::getModuleName()(),
+        subSystem: 'runAsync',
+        evtGroup: eventGroup,
+        millis: (new Date()).getTime(),
+        type: type
+      };
+      if (fragment != null) {
+        evt.fragment = [email protected]::intValue()();
+      }
+      if (size != null) {
+        evt.size = [email protected]::intValue()();
+      }
+      return evt;
+    }-*/;
+
+    private native boolean isStatsAvailable() /*-{
+      return !!$stats;
+    }-*/;
+  }
+
+  /**
+   * The standard loading strategy used in a web browser.
+   */
+  private static class XhrLoadingStrategy implements LoadingStrategy {
+    public void startLoadingFragment(int fragment,
+        final LoadErrorHandler loadErrorHandler) {
+      String fragmentUrl = gwtStartLoadingFragment(fragment,  
loadErrorHandler);
+
+      if (fragmentUrl == null) {
+        // The download has already started; nothing more to do
+        return;
+      }
+
+      // use XHR to download it
+
+      final XMLHttpRequest xhr = XMLHttpRequest.create();
+
+      xhr.open(HTTP_GET, fragmentUrl);
+
+      xhr.setOnReadyStateChange(new ReadyStateChangeHandler() {
+        public void onReadyStateChange(XMLHttpRequest ignored) {
+          if (xhr.getReadyState() == XMLHttpRequest.DONE) {
+            xhr.clearOnReadyStateChange();
+            if ((xhr.getStatus() == HTTP_STATUS_OK || xhr.getStatus() ==  
HTTP_STATUS_NON_HTTP)
+                && xhr.getResponseText() != null
+                && xhr.getResponseText().length() != 0) {
+              try {
+                gwtInstallCode(xhr.getResponseText());
+              } catch (RuntimeException e) {
+                loadErrorHandler.loadFailed(e);
+              }
+            } else {
+              loadErrorHandler.loadFailed(new HttpDownloadFailure(
+                  xhr.getStatus()));
+            }
+          }
+        }
+      });
+
+      xhr.send();
+    }
+
+    /**
+     * Call the linker-supplied <code>__gwtInstallCode</code> method. See  
the
+     * {...@link AsyncFragmentLoader class comment} for more details.
+     */
+    private native void gwtInstallCode(String text) /*-{
+      __gwtInstallCode(text);
+    }-*/;
+
+    /**
+     * Call the linker-supplied __gwtStartLoadingFragment function. It  
should
+     * either start the download and return null or undefined, or it  
should return
+     * a URL that should be downloaded to get the code. If it starts the  
download
+     * itself, it can synchronously load it, e.g. from cache, if that  
makes sense.
+     */
+    private native String gwtStartLoadingFragment(int fragment,
+        LoadErrorHandler loadErrorHandler) /*-{
+      function loadFailed(e) {
+         
loaderrorhandl...@com.google.gwt.core.client.impl.asyncfragmentloader$loaderrorhandler::loadFailed(Ljava/lang/Throwable;)(e);
+      }
+      return __gwtStartLoadingFragment(fragment, loadFailed);
+    }-*/;
+  }
+
+  /**
+   * The standard instance of AsyncFragmentLoader used in a web browser.  
The
+   * parameters to this call are filled in by
+   * {...@link com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs}.
+   */
+  public static AsyncFragmentLoader BROWSER_LOADER = new  
AsyncFragmentLoader(1,
+      new int[] {}, new XhrLoadingStrategy(), new StandardLogger());
+
    private static final String HTTP_GET = "GET";

    /**
@@ -162,63 +330,87 @@
    private static final int HTTP_STATUS_OK = 200;

    /**
+   * A helper static method that invokes
+   * BROWSER_LOADER.leftoversFragmentHasLoaded(). Such a call is generated  
by
+   * the compiler, as it is much simpler if there is a static method to  
wrap up
+   * the call.
+   */
+  public static void browserLoaderLeftoversFragmentHasLoaded() {
+    BROWSER_LOADER.leftoversFragmentHasLoaded();
+  }
+
+  /**
     * Error handlers for failure to download an initial fragment.
     *
     * TODO(spoon) make it a lightweight integer map
     */
-  private static Map<Integer, LoadErrorHandler>  
initialFragmentErrorHandlers = new HashMap<Integer, LoadErrorHandler>();
+  private Map<Integer, LoadErrorHandler> initialFragmentErrorHandlers =  
new HashMap<Integer, LoadErrorHandler>();

    /**
     * Indicates that the next fragment in {...@link  
#remainingInitialFragments} is
     * currently downloading.
     */
-  private static boolean initialFragmentsLoading = false;
+  private boolean initialFragmentsLoading = false;

    /**
     * The sequence of fragments to load initially, before anything else can  
be
     * loaded. This array will hold the initial sequence of bases followed  
by the
     * leftovers fragment. It is filled in by
-   * {...@link com.google.gwt.dev.jjs.impl.CodeSplitter}.  It does *not*  
include
-   * the leftovers fragment, which must be loaded once all of these are  
finished.
+   * {...@link com.google.gwt.dev.jjs.impl.CodeSplitter} modifying the  
initializer
+   * to {...@link #INSTANCE}. The list does <em>not</em> include the leftovers
+   * fragment, which must be loaded once all of these are finished.
     */
-  private static int[] initialLoadSequence = new int[] { };
+  private final int[] initialLoadSequence;
+
+  private LoadingStrategy loadingStrategy = new XhrLoadingStrategy();
+
+  private final Logger logger;

    /**
-   * The total number of split points in the program, counting the initial  
entry
-   * as an honorary split point. This is changed to the correct value by
-   * {...@link com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs}.
+   * The total number of entry points in the program, which is the number  
of
+   * split points plus one for the main entry point of the program.
     */
-  private static int numEntries = 1;
+  private final int numEntries;

    /**
     * Base fragments that remain to be downloaded. It is lazily initialized  
in
-   * the first call to {...@link #startLoadingNextInitial()}.  It does include
-   * the leftovers fragment.
+   * the first call to {...@link #startLoadingNextInitial()}. It does include  
the
+   * leftovers fragment.
     */
-  private static JsArrayInteger remainingInitialFragments = null;
+  private BoundedIntQueue remainingInitialFragments = null;

    /**
     * Split points that have been reached, but that cannot be downloaded  
until
-   * the initial fragments finish downloading.
+   * the initial fragments finish downloading. TODO(spoon) use something  
lighter
+   * than a LinkedList
     */
-  private static JsArrayInteger waitingForInitialFragments =  
createJsArrayInteger();
+  private final BoundedIntQueue waitingForInitialFragments;

    /**
     * Error handlers for the above queue.
     *
     * TODO(spoon) change this to a lightweight JS collection
     */
-  private static Queue<LoadErrorHandler>  
waitingForInitialFragmentsErrorHandlers = new  
LinkedList<LoadErrorHandler>();
+  private Queue<LoadErrorHandler> waitingForInitialFragmentsErrorHandlers  
= new LinkedList<LoadErrorHandler>();
+
+  public AsyncFragmentLoader(int numEntries, int[] initialLoadSequence,
+      LoadingStrategy loadingStrategy, Logger logger) {
+    this.numEntries = numEntries;
+    this.initialLoadSequence = initialLoadSequence;
+    this.loadingStrategy = loadingStrategy;
+    this.logger = logger;
+    waitingForInitialFragments = new BoundedIntQueue(numEntries + 1);
+  }

    /**
     * Inform the loader that a fragment has now finished loading.
     */
-  public static void fragmentHasLoaded(int fragment) {
+  public void fragmentHasLoaded(int fragment) {
      logFragmentLoaded(fragment);

      if (isInitial(fragment)) {
-      assert (fragment == remainingInitialFragments.get(0));
-      remainingInitialFragments.shift();
+      assert (fragment == remainingInitialFragments.peek());
+      remainingInitialFragments.remove();
        initialFragmentErrorHandlers.remove(fragment);

        startLoadingNextInitial();
@@ -230,7 +422,8 @@
     *
     * @param splitPoint the split point whose code needs to be loaded
     */
-  public static void inject(int splitPoint, LoadErrorHandler  
loadErrorHandler) {
+  public void inject(int splitPoint, LoadErrorHandler loadErrorHandler) {
+
      if (haveInitialFragmentsLoaded()) {
        /*
         * The initial fragments has loaded. Immediately start loading the
@@ -255,8 +448,8 @@
         * initial fragments have all been loaded.
         */

-      assert (waitingForInitialFragments.length() ==  
waitingForInitialFragmentsErrorHandlers.size());
-      waitingForInitialFragments.push(splitPoint);
+      assert (waitingForInitialFragments.size() ==  
waitingForInitialFragmentsErrorHandlers.size());
+      waitingForInitialFragments.add(splitPoint);
        waitingForInitialFragmentsErrorHandlers.add(loadErrorHandler);
      }

@@ -266,70 +459,28 @@
      if (!initialFragmentsLoading) {
        startLoadingNextInitial();
      }
-
-    return;
    }
-
-  public static void leftoversFragmentHasLoaded() {
+
+  public void leftoversFragmentHasLoaded() {
      fragmentHasLoaded(leftoversFragment());
    }

    /**
-   * Log an event with the lightweight metrics framework.
+   * Log an event with the {...@logger} this instance was provided.
     */
-  public static void logEventProgress(String eventGroup, String type) {
+  public void logEventProgress(String eventGroup, String type) {
      logEventProgress(eventGroup, type, null, null);
    }

-  private static native JsArrayInteger createJsArrayInteger() /*-{
-    return [];
-  }-*/;
-
-  private static native JavaScriptObject createStatsEvent(String  
eventGroup,
-      String type, Integer fragment, Integer size) /*-{
-    var evt = {
-     moduleName: @com.google.gwt.core.client.GWT::getModuleName()(),
-      subSystem: 'runAsync',
-      evtGroup: eventGroup,
-      millis: (new Date()).getTime(),
-      type: type
-    };
-    if (fragment != null) {
-      evt.fragment = [email protected]::intValue()();
-    }
-    if (size != null) {
-      evt.size = [email protected]::intValue()();
-    }
-    return evt;
-  }-*/;
-
-  private static native void gwtInstallCode(String text) /*-{
-    __gwtInstallCode(text);
-  }-*/;
-
-  /**
-   * Call the linker-supplied __gwtStartLoadingFragment function. It should
-   * either start the download and return null or undefined, or it should  
return
-   * a URL that should be downloaded to get the code. If it starts the  
download
-   * itself, it can synchronously load it, e.g. from cache, if that makes  
sense.
-   */
-  private static native String gwtStartLoadingFragment(int fragment,
-      LoadErrorHandler loadErrorHandler) /*-{
-    function loadFailed(e) {
-       
loaderrorhandl...@com.google.gwt.core.client.impl.asyncfragmentloader$loaderrorhandler::loadFailed(Ljava/lang/Throwable;)(e);
-    }
-    return __gwtStartLoadingFragment(fragment, loadFailed);
-  }-*/;
-
    /**
     * Return whether all initial fragments have completed loading.
     */
-  private static boolean haveInitialFragmentsLoaded() {
+  private boolean haveInitialFragmentsLoaded() {
      return remainingInitialFragments != null
-        && remainingInitialFragments.length() == 0;
+        && remainingInitialFragments.size() == 0;
    }

-  private static boolean isInitial(int splitPoint) {
+  private boolean isInitial(int splitPoint) {
      if (splitPoint == leftoversFragment()) {
        return true;
      }
@@ -341,83 +492,49 @@
      return false;
    }

-  private static native boolean isStatsAvailable() /*-{
-    return !!$stats;
-  }-*/;
-
-  private static int leftoversFragment() {
+  private int leftoversFragment() {
      return numEntries;
    }

    /**
-   * Log an event with the lightweight metrics framework. The
+   * Log event progress via the {...@link Logger} this instance was provided.  
The
     * <code>fragment</code> and <code>size</code> objects are allowed to be
     * <code>null</code>.
     */
-  private static void logEventProgress(String eventGroup, String type,
+  private void logEventProgress(String eventGroup, String type,
        Integer fragment, Integer size) {
-    @SuppressWarnings("unused")
-    boolean toss = isStatsAvailable()
-        && stats(createStatsEvent(eventGroup, type, fragment, size));
+    logger.logEventProgress(eventGroup, type, fragment, size);
    }

-  private static void logFragmentLoaded(int fragment) {
+  private void logFragmentLoaded(int fragment) {
      String logGroup = (fragment == leftoversFragment())
          ? LwmLabels.LEFTOVERS_DOWNLOAD : LwmLabels.downloadGroup(fragment);
      logEventProgress(logGroup, LwmLabels.END, fragment, null);
    }

-  private static void startLoadingFragment(int fragment,
+  private void startLoadingFragment(int fragment,
        final LoadErrorHandler loadErrorHandler) {
-    String fragmentUrl = gwtStartLoadingFragment(fragment,  
loadErrorHandler);
-
-    if (fragmentUrl != null) {
-      // use XHR
-      final XMLHttpRequest xhr = XMLHttpRequest.create();
-
-      xhr.open(HTTP_GET, fragmentUrl);
-
-      xhr.setOnReadyStateChange(new ReadyStateChangeHandler() {
-        public void onReadyStateChange(XMLHttpRequest xhr) {
-          if (xhr.getReadyState() == XMLHttpRequest.DONE) {
-            xhr.clearOnReadyStateChange();
-            if ((xhr.getStatus() == HTTP_STATUS_OK || xhr.getStatus() ==  
HTTP_STATUS_NON_HTTP)
-                && xhr.getResponseText() != null
-                && xhr.getResponseText().length() != 0) {
-              try {
-                gwtInstallCode(xhr.getResponseText());
-              } catch (RuntimeException e) {
-                loadErrorHandler.loadFailed(e);
-              }
-            } else {
-              loadErrorHandler.loadFailed(new HttpDownloadFailure(
-                  xhr.getStatus()));
-            }
-          }
-        }
-      });
-
-      xhr.send();
-    }
+    loadingStrategy.startLoadingFragment(fragment, loadErrorHandler);
    }

    /**
     * Start downloading the next fragment in the initial sequence, if there  
are
     * any left.
     */
-  private static void startLoadingNextInitial() {
+  private void startLoadingNextInitial() {
      if (remainingInitialFragments == null) {
        // first call, so initialize remainingInitialFragments
-      remainingInitialFragments = createJsArrayInteger();
+      remainingInitialFragments = new BoundedIntQueue(
+          initialLoadSequence.length + 1);
        for (int sp : initialLoadSequence) {
-        remainingInitialFragments.push(sp);
+        remainingInitialFragments.add(sp);
        }
-      remainingInitialFragments.push(leftoversFragment());
+      remainingInitialFragments.add(leftoversFragment());
      }
-
+
      if (initialFragmentErrorHandlers.isEmpty()
          && waitingForInitialFragmentsErrorHandlers.isEmpty()
-        && remainingInitialFragments.length() > 1) {
+        && remainingInitialFragments.size() > 1) {
        /*
         * No further requests are pending, and more than the leftovers  
fragment
         * is left outstanding. Stop loading stuff for now.
@@ -426,12 +543,12 @@
        return;
      }

-    if (remainingInitialFragments.length() > 0) {
+    if (remainingInitialFragments.size() > 0) {
        // start loading the next initial fragment
        initialFragmentsLoading = true;
-      int nextSplitPoint = remainingInitialFragments.get(0);
-      logEventProgress(LwmLabels.downloadGroup(nextSplitPoint),  
LwmLabels.BEGIN,
-          nextSplitPoint, null);
+      int nextSplitPoint = remainingInitialFragments.peek();
+      logEventProgress(LwmLabels.downloadGroup(nextSplitPoint),
+          LwmLabels.BEGIN, nextSplitPoint, null);
        startLoadingFragment(nextSplitPoint, new  
InitialFragmentDownloadFailed());
        return;
      }
@@ -439,20 +556,12 @@
      // all initials are finished
      initialFragmentsLoading = false;
      assert (haveInitialFragmentsLoaded());
-
+
      // start loading any pending fragments
-    assert (waitingForInitialFragments.length() ==  
waitingForInitialFragmentsErrorHandlers.size());
-    while (waitingForInitialFragments.length() > 0) {
-      startLoadingFragment(waitingForInitialFragments.shift(),
+    assert (waitingForInitialFragments.size() ==  
waitingForInitialFragmentsErrorHandlers.size());
+    while (waitingForInitialFragments.size() > 0) {
+      startLoadingFragment(waitingForInitialFragments.remove(),
            waitingForInitialFragmentsErrorHandlers.remove());
      }
    }
-
-  /**
-   * Always use this as {...@link isStatsAvailable} &amp;&amp;
-   * {...@link #stats(JavaScriptObject)}.
-   */
-  private static native boolean stats(JavaScriptObject data) /*-{
-    return $stats(data);
-  }-*/;
  }

Modified: trunk/user/test/com/google/gwt/core/CoreSuite.java
==============================================================================
--- trunk/user/test/com/google/gwt/core/CoreSuite.java  (original)
+++ trunk/user/test/com/google/gwt/core/CoreSuite.java  Thu Jul 16 16:35:08  
2009
@@ -19,6 +19,7 @@
  import com.google.gwt.core.client.HttpThrowableReporterTest;
  import com.google.gwt.core.client.JavaScriptExceptionTest;
  import com.google.gwt.core.client.JsArrayTest;
+import com.google.gwt.core.client.impl.AsyncFragmentLoaderTest;
  import com.google.gwt.core.client.impl.StackTraceCreatorTest;
  import com.google.gwt.junit.tools.GWTTestSuite;

@@ -37,6 +38,7 @@
      suite.addTestSuite(JsArrayTest.class);
      suite.addTestSuite(GWTTest.class);
      suite.addTestSuite(StackTraceCreatorTest.class);
+    suite.addTestSuite(AsyncFragmentLoaderTest.class);
      // $JUnit-END$

      return suite;

Added:  
trunk/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java
==============================================================================
--- (empty file)
+++  
trunk/user/test/com/google/gwt/core/client/impl/AsyncFragmentLoaderTest.java    
 
Thu Jul 16 16:35:08 2009
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+import  
com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadErrorHandler;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader.Logger;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class AsyncFragmentLoaderTest extends TestCase {
+  private static class MockErrorHandler implements LoadErrorHandler {
+    private boolean wasCalled = false;
+
+    public boolean getWasCalled() {
+      return wasCalled;
+    }
+
+    public void loadFailed(Throwable reason) {
+      wasCalled = true;
+    }
+  }
+
+  private static class MockLoadStrategy implements LoadingStrategy {
+    public final Map<Integer, LoadErrorHandler> errorHandlers = new  
HashMap<Integer, LoadErrorHandler>();
+    private List<Integer> loadRequests = new LinkedList<Integer>();
+
+    public void assertFragmentsRequested(int... expectedAry) {
+      List<Integer> actual = new ArrayList<Integer>(loadRequests);
+      loadRequests.clear();
+      List<Integer> expected = toList(expectedAry);
+
+      if (!sameContents(actual, expected)) {
+        fail("Expected= " + commaSeparated(expected) + "; actual="
+            + commaSeparated(actual));
+      }
+    }
+
+    public void startLoadingFragment(int fragment,
+        LoadErrorHandler loadErrorHandler) {
+      errorHandlers.put(fragment, loadErrorHandler);
+      loadRequests.add(fragment);
+    }
+
+    private String commaSeparated(List<Integer> ary) {
+      StringBuilder sb = new StringBuilder();
+      boolean first = true;
+      for (Integer x : ary) {
+        if (first) {
+          first = false;
+        } else {
+          sb.append(",");
+        }
+        sb.append(x);
+      }
+      return sb.toString();
+    }
+
+    private boolean sameContents(List<Integer> actual, List<Integer>  
expected) {
+      if (actual.size() != expected.size()) {
+        return false;
+      }
+      for (int i = 0; i < actual.size(); i++) {
+        if (!actual.get(i).equals(expected.get(i))) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    private List<Integer> toList(int[] ary) {
+      List<Integer> list = new ArrayList<Integer>();
+      for (int i = 0; i < ary.length; i++) {
+        list.add(ary[i]);
+      }
+      return list;
+    }
+  }
+
+  private static final LoadErrorHandler NULL_ERROR_HANDLER = new  
LoadErrorHandler() {
+    public void loadFailed(Throwable reason) {
+    }
+  };
+
+  private static final Logger NULL_LOGGER = new Logger() {
+    public void logEventProgress(String eventGroup, String type,
+        Integer fragment, Integer size) {
+    }
+  };
+
+  private static Throwable makeLoadFailedException() {
+    return new RuntimeException("Load Failed");
+  }
+
+  public void testBasics() {
+    MockLoadStrategy reqs = new MockLoadStrategy();
+    int numEntries = 5;
+    AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries,
+        new int[] {}, reqs, NULL_LOGGER);
+
+    loader.inject(1, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(numEntries);
+    loader.leftoversFragmentHasLoaded();
+    reqs.assertFragmentsRequested(1);
+    loader.fragmentHasLoaded(1);
+
+    loader.inject(2, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(2);
+    loader.fragmentHasLoaded(2);
+  }
+
+  /**
+   * Check the behavior when there are download failures.
+   */
+  public void testDownloadFailures() {
+    MockLoadStrategy reqs = new MockLoadStrategy();
+    int numEntries = 6;
+    AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new  
int[] {
+        1, 2, 3}, reqs, NULL_LOGGER);
+
+    // request fragment 1
+    MockErrorHandler error1try1 = new MockErrorHandler();
+    loader.inject(1, error1try1);
+    reqs.assertFragmentsRequested(1);
+
+    // fragment 1 fails
+    loadFailed(reqs, 1);
+    assertTrue(error1try1.getWasCalled());
+
+    // try again on fragment 1
+    MockErrorHandler error1try2 = new MockErrorHandler();
+    loader.inject(1, error1try2);
+    reqs.assertFragmentsRequested(1);
+
+    // this time fragment 1 succeeds
+    loader.fragmentHasLoaded(1);
+    reqs.assertFragmentsRequested();
+    assertFalse(error1try2.getWasCalled());
+
+    // request a later initial fragment (3), and see what happens if an
+    // intermediate download fails
+    MockErrorHandler error3try1 = new MockErrorHandler();
+    loader.inject(3, error3try1);
+    reqs.assertFragmentsRequested(2);
+
+    loadFailed(reqs, 2);
+    assertTrue(error3try1.wasCalled);
+
+    // request both 3 and 5, an see what happens if
+    // the leftovers download fails
+    MockErrorHandler error3try2 = new MockErrorHandler();
+    MockErrorHandler error5try1 = new MockErrorHandler();
+    loader.inject(3, error3try2);
+    loader.inject(5, error5try1);
+    reqs.assertFragmentsRequested(2);
+
+    loader.fragmentHasLoaded(2);
+    reqs.assertFragmentsRequested(3);
+
+    loader.fragmentHasLoaded(3);
+    reqs.assertFragmentsRequested(numEntries);
+
+    loadFailed(reqs, numEntries); // leftovers fails!
+    assertFalse(error3try2.getWasCalled()); // 3 should have succeeded
+    assertTrue(error5try1.getWasCalled()); // 5 failed
+    reqs.errorHandlers.get(numEntries);
+
+    // now try 5 again, and have everything succeed
+    MockErrorHandler error5try2 = new MockErrorHandler();
+    loader.inject(5, error5try2);
+    reqs.assertFragmentsRequested(numEntries);
+
+    loader.leftoversFragmentHasLoaded();
+    reqs.assertFragmentsRequested(5);
+
+    loader.fragmentHasLoaded(5);
+    reqs.assertFragmentsRequested();
+    assertFalse(error5try2.getWasCalled());
+  }
+
+  /**
+   * If only the first part of the initial load sequence is requested, then
+   * don't request more.
+   */
+  public void testLoadingPartOfInitialSequence() {
+    MockLoadStrategy reqs = new MockLoadStrategy();
+    int numEntries = 6;
+    AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new  
int[] {
+        1, 2, 3}, reqs, NULL_LOGGER);
+
+    loader.inject(1, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(1);
+
+    loader.fragmentHasLoaded(1);
+    reqs.assertFragmentsRequested(); // should stop
+
+    loader.inject(2, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(2);
+
+    loader.fragmentHasLoaded(2);
+    reqs.assertFragmentsRequested(); // again, should stop
+
+    loader.inject(3, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(3);
+
+    loader.fragmentHasLoaded(3);
+    reqs.assertFragmentsRequested(numEntries); // last initial, so it  
should
+    // request the leftovers
+
+    loader.fragmentHasLoaded(numEntries);
+    reqs.assertFragmentsRequested();
+
+    // check that exclusives now load
+    loader.inject(5, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(5);
+
+    loader.fragmentHasLoaded(5);
+    reqs.assertFragmentsRequested();
+  }
+
+  /**
+   * A thorough exercise of loading with an initial load sequence  
specified.
+   */
+  public void testWithInitialLoadSequence() {
+    MockLoadStrategy reqs = new MockLoadStrategy();
+    int numEntries = 6;
+    AsyncFragmentLoader loader = new AsyncFragmentLoader(numEntries, new  
int[] {
+        1, 2, 3}, reqs, NULL_LOGGER);
+
+    loader.inject(1, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(1);
+
+    loader.inject(3, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(); // still waiting on fragment 1
+
+    loader.inject(5, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(); // still waiting on fragment 1
+
+    // say that 1 loads, which should trigger a chain of backlogged  
downloads
+    loader.fragmentHasLoaded(1);
+    reqs.assertFragmentsRequested(2); // next initial
+
+    loader.fragmentHasLoaded(2);
+    reqs.assertFragmentsRequested(3); // next initial
+
+    loader.fragmentHasLoaded(3);
+    reqs.assertFragmentsRequested(numEntries); // leftovers
+
+    loader.leftoversFragmentHasLoaded();
+    reqs.assertFragmentsRequested(5);
+
+    loader.fragmentHasLoaded(5);
+    reqs.assertFragmentsRequested(); // quiescent
+
+    // check that new exclusive fragment requests work
+    loader.inject(4, NULL_ERROR_HANDLER);
+    reqs.assertFragmentsRequested(4);
+    loader.fragmentHasLoaded(4);
+    reqs.assertFragmentsRequested();
+  }
+
+  private void loadFailed(MockLoadStrategy reqs, int fragment) {
+    reqs.errorHandlers.get(fragment).loadFailed(makeLoadFailedException());
+  }
+}

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to