Author: johnh
Date: Sat Mar 26 05:18:14 2011
New Revision: 1085647

URL: http://svn.apache.org/viewvc?rev=1085647&view=rev
Log:
Reintroduce Closure Compiler to Shindig, and implement Java 1.5/1.6-specific 
JsCompiler bindings.

This CL reintroduces a Closure-based JsCompiler implementation, and uses Paul 
Lindner's Maven work to ensure
that it can be built for deployments utilizing Java 1.6. Java 1.5 gracefully 
falls back to the build-time compilation mechanism
in ExportJsCompiler, which does not depend on a 1.6-specific package.


Added:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JsServingPipelineModule.java
      - copied, changed from r1085589, 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JavascriptModule.java
    shindig/trunk/java/gadgets/src/main/java15/org/apache/shindig/gadgets/js/
    
shindig/trunk/java/gadgets/src/main/java15/org/apache/shindig/gadgets/js/JsCompilerModule.java
    shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/js/
    
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/js/JsCompilerModule.java
    
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/
    
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/js/
    
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompiler.java
    shindig/trunk/java/gadgets/src/test/java16/
    shindig/trunk/java/gadgets/src/test/java16/org/
    shindig/trunk/java/gadgets/src/test/java16/org/apache/
    shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/
    shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/
    
shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/
    
shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/js/
    
shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompilerTest.java
Removed:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JavascriptModule.java
Modified:
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompiler.java
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompilerTest.java
    shindig/trunk/pom.xml

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java?rev=1085647&r1=1085646&r2=1085647&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
 Sat Mar 26 05:18:14 2011
@@ -34,7 +34,8 @@ import org.apache.shindig.gadgets.config
 import org.apache.shindig.gadgets.http.AbstractHttpCache;
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.http.InvalidationHandler;
-import org.apache.shindig.gadgets.js.JavascriptModule;
+import org.apache.shindig.gadgets.js.JsCompilerModule;
+import org.apache.shindig.gadgets.js.JsServingPipelineModule;
 import org.apache.shindig.gadgets.parse.ParseModule;
 import org.apache.shindig.gadgets.preload.PreloadModule;
 import org.apache.shindig.gadgets.render.RenderModule;
@@ -76,7 +77,8 @@ public class DefaultGuiceModule extends 
     install(new SubstituterModule());
     install(new TemplateModule());
     install(new UriModule());
-    install(new JavascriptModule());
+    install(new JsCompilerModule());
+    install(new JsServingPipelineModule());
 
     // 
bind(Long.class).annotatedWith(Names.named("org.apache.shindig.serviceExpirationDurationMinutes")).toInstance(60l);
 

Copied: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JsServingPipelineModule.java
 (from r1085589, 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JavascriptModule.java)
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JsServingPipelineModule.java?p2=shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JsServingPipelineModule.java&p1=shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JavascriptModule.java&r1=1085589&r2=1085647&rev=1085647&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JavascriptModule.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/js/JsServingPipelineModule.java
 Sat Mar 26 05:18:14 2011
@@ -25,13 +25,10 @@ import com.google.inject.Provides;
 
 import java.util.List;
 
-import org.apache.shindig.gadgets.rewrite.js.ExportJsCompiler;
-import org.apache.shindig.gadgets.rewrite.js.JsCompiler;
-
 /**
  * Guice configuration for the Javascript serving pipeline.
  */
-public class JavascriptModule extends AbstractModule {
+public class JsServingPipelineModule extends AbstractModule {
 
   @Override
   protected void configure() {
@@ -40,12 +37,6 @@ public class JavascriptModule extends Ab
   
   @Provides
   @Inject
-  public JsCompiler provideJsCompiler(ExportJsCompiler compiler) {
-    return compiler;
-  }
-  
-  @Provides
-  @Inject
   public List<JsProcessor> provideProcessors(
       InjectJsInfoVariableProcessor injectJsInfoVariableProcessor,
       JsLoadProcessor jsLoaderGeneratorProcessor,

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompiler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompiler.java?rev=1085647&r1=1085646&r2=1085647&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompiler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompiler.java
 Sat Mar 26 05:18:14 2011
@@ -83,11 +83,6 @@ public class ExportJsCompiler extends De
     return builder.build();
   }
 
-  @Override
-  protected String getFeatureContent(JsUri jsUri, FeatureResource resource) {
-    return resource.getDebugContent();
-  }
-
   private JsContent getExportsForFeature(JsUri jsUri, FeatureBundle bundle) {
     List<String> exports = Lists.newArrayList();
 

Added: 
shindig/trunk/java/gadgets/src/main/java15/org/apache/shindig/gadgets/js/JsCompilerModule.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java15/org/apache/shindig/gadgets/js/JsCompilerModule.java?rev=1085647&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java15/org/apache/shindig/gadgets/js/JsCompilerModule.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/main/java15/org/apache/shindig/gadgets/js/JsCompilerModule.java
 Sat Mar 26 05:18:14 2011
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.js;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+
+import org.apache.shindig.gadgets.rewrite.js.JsCompiler;
+import org.apache.shindig.gadgets.rewrite.js.ExportJsCompiler;
+
+import java.util.List;
+
+/**
+ * Guice configuration for the Javascript compilation.
+ */
+public class JsCompilerModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    // nothing to configure here
+  }
+
+  @Provides
+  @Inject
+  public JsCompiler provideJsCompiler(ExportJsCompiler compiler) {
+    return compiler;
+  }
+  
+}

Added: 
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/js/JsCompilerModule.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/js/JsCompilerModule.java?rev=1085647&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/js/JsCompilerModule.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/js/JsCompilerModule.java
 Sat Mar 26 05:18:14 2011
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.js;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+
+import org.apache.shindig.gadgets.rewrite.js.ExportJsCompiler;
+import org.apache.shindig.gadgets.rewrite.js.JsCompiler;
+
+import java.util.List;
+
+/**
+ * Guice configuration for JS compilation.
+ */
+public class JsCompilerModule extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    // nothing to configure here
+  }
+
+  @Provides
+  @Inject
+  public JsCompiler provideJsCompiler(ExportJsCompiler compiler) {
+    return compiler;
+  }
+  
+}

Added: 
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompiler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompiler.java?rev=1085647&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompiler.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/main/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompiler.java
 Sat Mar 26 05:18:14 2011
@@ -0,0 +1,396 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.shindig.gadgets.rewrite.js;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+import com.google.javascript.jscomp.BasicErrorManager;
+import com.google.javascript.jscomp.CheckLevel;
+import com.google.javascript.jscomp.CompilationLevel;
+import com.google.javascript.jscomp.Compiler;
+import com.google.javascript.jscomp.CompilerOptions;
+import com.google.javascript.jscomp.JSError;
+import com.google.javascript.jscomp.JSSourceFile;
+import com.google.javascript.jscomp.Result;
+import com.google.javascript.jscomp.SourceMap;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.shindig.common.cache.Cache;
+import org.apache.shindig.common.cache.CacheProvider;
+import org.apache.shindig.common.util.HashUtil;
+import org.apache.shindig.gadgets.features.ApiDirective;
+import org.apache.shindig.gadgets.features.FeatureRegistry;
+import org.apache.shindig.gadgets.features.FeatureRegistry.FeatureBundle;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.js.JsContent;
+import org.apache.shindig.gadgets.js.JsException;
+import org.apache.shindig.gadgets.js.JsResponse;
+import org.apache.shindig.gadgets.js.JsResponseBuilder;
+import org.apache.shindig.gadgets.rewrite.js.ExportJsCompiler;
+import org.apache.shindig.gadgets.rewrite.js.JsCompiler;
+import org.apache.shindig.gadgets.uri.JsUriManager.JsUri;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class ClosureJsCompiler implements JsCompiler {
+  // Based on Closure Library's goog.exportSymbol implementation.
+  private static final JsContent EXPORTSYMBOL_CODE =
+      new JsContent("var goog=goog||{};goog.exportSymbol=function(name,obj){"
+              + "var parts=name.split('.'),cur=window,part;"
+              + "for(;parts.length&&(part=parts.shift());){if(!parts.length){"
+              + "cur[part]=obj;}else{cur=cur[part]||(cur[part]={})}}};", 
"[goog.exportSymbol]");
+
+  private static final JSSourceFile[] JSSOURCE_TYPE = new JSSourceFile[0];
+
+  @VisibleForTesting
+  static final String CACHE_NAME = "CompiledJs";
+
+  private final ExportJsCompiler exportCompiler;
+  private final CompilerOptions options;
+  private final Cache<String, ClosureResult> cache;
+  private ClosureResult lastResult;
+
+  @Inject
+  public ClosureJsCompiler(CacheProvider cacheProvider, FeatureRegistry 
registry) {
+    this(newCompilerOptions(), cacheProvider, registry);
+  }
+
+  public ClosureJsCompiler(CompilerOptions options, CacheProvider 
cacheProvider,
+      FeatureRegistry registry) {
+    this(options, new ExportJsCompiler(registry), cacheProvider);
+  }
+
+  @VisibleForTesting
+  ClosureJsCompiler(CompilerOptions options, ExportJsCompiler exportCompiler,
+      CacheProvider cacheProvider) {
+    // TODO: Consider using Provider<Compiler> here.
+    this.options = options;
+    this.cache = cacheProvider.createCache(CACHE_NAME);
+    this.exportCompiler = exportCompiler;
+  }
+
+  public static CompilerOptions newCompilerOptions() {
+    // Same as 
google3/javascript/closure/builddefs:CLOSURE_COMPILER_FLAGS_FULL.
+    // Flags are used/preferred by Gmail.
+    CompilerOptions result = new CompilerOptions();
+    
CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(result);
+    result.removeUnusedPrototypePropertiesInExterns = false;
+    // Avoid multiple declarations of tamings___ variables.
+    // TODO: Get rid of this deviation from standard flags.
+    result.checkSymbols = false;
+    result.sourceMapOutputPath = "create.out";
+    result.sourceMapDetailLevel = SourceMap.DetailLevel.ALL;
+
+    return result;
+  }
+
+  @VisibleForTesting
+  Compiler newCompiler() {
+    BasicErrorManager errorManager = new BasicErrorManager() {
+      @Override
+      protected void printSummary() { /* Do nothing */ }
+
+      @Override
+      public void println(CheckLevel arg0, JSError arg1) { /* Do nothing */ }
+    };
+    return new Compiler(errorManager);
+  }
+
+  private String toExternString(List<String> externs) {
+    StringBuilder builder = new StringBuilder();
+    for (String extern : externs) {
+      builder.append(extern).append(";\n");
+    }
+    return builder.toString();
+  }
+
+  public JsResponse compile(JsUri jsUri, Iterable<JsContent> content, 
List<String> externs) 
+      throws JsException {
+    JsResponse exportResponse = exportCompiler.compile(jsUri, content, 
externs);
+    content = exportResponse.getAllJsContent();
+
+    String externStr = toExternString(externs);
+    String cacheKey = makeCacheKey(exportResponse.toJsString(), externStr, 
jsUri);
+    ClosureResult cachedResult = cache.getElement(cacheKey);
+    if (cachedResult != null) {
+      lastResult = cachedResult;
+      return cachedResult.response;
+    }
+
+    JsResponseBuilder builder = new JsResponseBuilder();
+    String theExterns = null;
+    
+    // Only run actual compiler if necessary.
+    if (!jsUri.isDebug() || options.isExternExportsEnabled()) {
+      List<JSSourceFile> allExterns = Lists.newArrayList();
+      allExterns.add(JSSourceFile.fromCode("externs", externStr));
+
+      List<JsContent> allContent = Lists.newLinkedList(content);
+      if (options.isExternExportsEnabled()) {
+        allContent.add(EXPORTSYMBOL_CODE);
+      }
+
+      Compiler actualCompiler = newCompiler();
+      Result result = actualCompiler.compile(
+          allExterns.toArray(JSSOURCE_TYPE),
+          convertToJsSource(allContent).toArray(JSSOURCE_TYPE),
+          options);
+
+      if (actualCompiler.hasErrors()) {
+        ImmutableList.Builder<String> errors = ImmutableList.builder();
+        for (JSError error : actualCompiler.getErrors()) {
+          errors.add(error.toString());
+        }
+        builder.setStatusCode(HttpResponse.SC_NOT_FOUND)
+            .addErrors(errors.build()).build();
+        ClosureResult errorResult = new ClosureResult(builder.build(), null);
+        cache.addElement(cacheKey, errorResult);
+        return errorResult.response;
+      }
+      
+      String compiled = actualCompiler.toSource();
+      if (outputCorrelatedJs()) {
+        // Emit code correlated w/ original source.
+        // This operation is equivalent in final code to bundled-output,
+        // but is less efficient and should perhaps only be used in code 
profiling.
+        SourceMappings sm = processSourceMap(result, allContent);      
+        builder.appendAllJs(sm.mapCompiled(compiled));
+      } else {
+        builder.appendJs(compiled, "[compiled]");
+      }
+      
+      theExterns = result.externExport;
+    } else {
+      // Otherwise, return original content and null exports.
+      builder.appendAllJs(content);
+    }
+
+    lastResult = new ClosureResult(builder.build(), theExterns);
+    cache.addElement(cacheKey, lastResult);
+    return lastResult.response;
+  }
+  
+  // Override this method to return "true" for cases where individual chunks of
+  // compiled JS should be emitted as JsContent objects, each correlating 
output JS
+  // with the original source file from which they came.
+  protected boolean outputCorrelatedJs() {
+    return false;
+  }
+  
+  private List<JSSourceFile> convertToJsSource(Iterable<JsContent> content) {
+    Map<String, Integer> sourceMap = Maps.newHashMap();
+    List<JSSourceFile> sources = Lists.newLinkedList();
+    for (JsContent src : content) {
+      sources.add(JSSourceFile.fromCode(getUniqueSrc(src.getSource(), 
sourceMap), src.get()));
+    }
+    return sources;
+  }
+  
+  private static String getUniqueSrc(String source, Map<String, Integer> 
sourceMap) {
+    Integer ix = sourceMap.get(source);
+    if (ix == null) {
+      ix = 0;
+    }
+    String ret = source + (ix > 0 ? ":" + ix : "");
+    sourceMap.put(source, ix + 1);
+    return ret;
+  }
+  
+  private static String getRootSrc(String source) {
+    int colIx = source.lastIndexOf(":");
+    if (colIx == -1) {
+      return source;
+    }
+    return source.substring(0, colIx);
+  }
+
+  public Iterable<JsContent> getJsContent(JsUri jsUri, FeatureBundle bundle) {
+    jsUri = new JsUri(jsUri) {
+      @Override
+      public boolean isDebug() {
+        // Force debug JS in the raw JS content retrieved.
+        return true;
+      }
+    };
+    List<JsContent> builder = 
Lists.newLinkedList(exportCompiler.getJsContent(jsUri, bundle));
+
+    if (options.isExternExportsEnabled()) {
+      List<String> exports = 
Lists.newArrayList(bundle.getApis(ApiDirective.Type.JS, true));
+      Collections.sort(exports);
+      String prevExport = null;
+      for (String export : exports) {
+        if (!export.equals(prevExport)) {
+          builder.add(new JsContent("goog.exportSymbol('" + 
StringEscapeUtils.escapeJavaScript(export) +
+              "', " + export + ");\n", "[export-symbol]"));
+          prevExport = export;
+        }
+      }
+    }
+    return builder;
+  }
+
+  public ClosureResult getLastResult() {
+    return this.lastResult;
+  }
+
+  protected String makeCacheKey(String code, String externs, JsUri uri) {
+    // TODO: include compilation options in the cache key
+    return Joiner.on(":").join(
+        HashUtil.checksum(code.getBytes()),
+        HashUtil.checksum(externs.getBytes()),
+        uri.getCompileMode(),
+        uri.isDebug());
+  }
+
+  public static class ClosureResult {
+    private final JsResponse response;
+    private final String externs;
+
+    public ClosureResult(JsResponse response, String externs) {
+      this.response = response;
+      this.externs = externs;
+    }
+
+    public String getExterns() {
+      return externs;
+    }
+  }
+  
+  /**
+   * Pull the source map out of the given Closure Result, and convert
+   * it to a local SourceMappings object used to correlate compiled
+   * content with originating source.
+   * @param result Closure result object with source map.
+   * @param allInputs All inputs supplied to the compiler, in JsContent form.
+   * @return SourceMappings object correlating compiled with originating input.
+   */
+  private SourceMappings processSourceMap(Result result, List<JsContent> 
allInputs) 
+      throws JsException {
+    StringBuilder sb = new StringBuilder();
+    try {
+      result.sourceMap.appendTo(sb, "done");
+      return SourceMappings.parseV1(sb.toString(), allInputs);
+    } catch (Exception e) {
+      throw new JsException(HttpResponse.SC_INTERNAL_SERVER_ERROR,
+          "Parse error for source map: " + e);
+    }
+  }
+  
+  private static class SourceMappings {
+    private final Map<String, JsContent> orig;
+    private final int[][] lines;
+    private final String[] mappings;
+    
+    private SourceMappings(int[][] lines, String[] mappings, List<JsContent> 
content) {
+      this.lines = lines;
+      this.mappings = mappings;
+      this.orig = Maps.newHashMap();
+      for (JsContent js : content) {
+        orig.put(js.getSource(), js);
+      }
+    }
+    
+    private List<JsContent> mapCompiled(String compiled) {
+      List<JsContent> compiledOut = Lists.newLinkedList();
+      int codeStart = 0;
+      int codePos = 0;
+      int curMapping = -1;
+      for (int line = 0; line < lines.length; ++line) {
+        for (int col = 0; col < lines[line].length; ++col) {
+          int nextMapping = lines[line][col];
+          codePos++;
+          if (nextMapping != curMapping && curMapping != -1) {
+            JsContent sourceJs = orig.get(getRootSrc(mappings[curMapping]));
+            compiledOut.add(new JsContent(compiled.substring(codeStart, 
codePos),
+                sourceJs.getSource(), sourceJs.getFeature()));
+            codeStart = codePos;
+          }
+          curMapping = nextMapping;
+        }
+      }
+      JsContent lastSource = orig.get(getRootSrc(mappings[curMapping]));
+      compiledOut.add(new JsContent(compiled.substring(codeStart, codePos + 1),
+          lastSource.getSource(), lastSource.getFeature()));
+      return compiledOut;
+    }
+    
+    private static final String BEGIN_COMMENT = "/*";
+    private static final String END_COMMENT = "*/";
+    private static SourceMappings parseV1(String sourcemap, List<JsContent> 
orig)
+        throws IOException, JSONException {
+      BufferedReader reader = new BufferedReader(new StringReader(sourcemap));
+      JSONObject summary = new JSONObject(stripComment(reader.readLine()));
+      
+      int lineCount = summary.getInt("count");
+      
+      // Read lines info.
+      int maxMappingIndex = 0;
+      int[][] lines = new int[lineCount][];
+      for (int i = 0; i < lineCount; ++i) {
+        String lineDescriptor = reader.readLine();
+        JSONArray lineArr = new JSONArray(lineDescriptor);
+        lines[i] = new int[lineArr.length()];
+        for (int j = 0; j < lineArr.length(); ++j) {
+          int mappingIndex = lineArr.getInt(j);
+          lines[i][j] = mappingIndex;
+          maxMappingIndex = Math.max(mappingIndex, maxMappingIndex);
+        }
+      }
+      
+      // Bypass comment and unused file info for each line.
+      reader.readLine(); // comment
+      for (int i = 0; i < lineCount; ++i) {
+        reader.readLine();
+      }
+      
+      // Read mappings objects.
+      reader.readLine(); // comment
+      String[] mappings = new String[maxMappingIndex + 1];
+      for (int i = 0; i <= maxMappingIndex; ++i) {
+        String mappingLine = reader.readLine();
+        JSONArray mappingObj = new JSONArray(mappingLine);
+        mappings[i] = mappingObj.getString(0);
+      }
+      
+      return new SourceMappings(lines, mappings, orig);
+    }
+    
+    private static String stripComment(String line) {
+      int begin = line.indexOf(BEGIN_COMMENT);
+      if (begin != -1) {
+        int end = line.indexOf(END_COMMENT, begin + 1);
+        if (end != -1) {
+          return line.substring(0, begin) + line.substring(end + 
END_COMMENT.length());
+        }
+      }
+      return line;
+    }
+  }
+}

Modified: 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompilerTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompilerTest.java?rev=1085647&r1=1085646&r2=1085647&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompilerTest.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/ExportJsCompilerTest.java
 Sat Mar 26 05:18:14 2011
@@ -97,7 +97,7 @@ public class ExportJsCompilerTest {
     expect(result.getContext()).andReturn(RenderingContext.GADGET).anyTimes();
     expect(result.getContainer()).andReturn(CONTAINER).anyTimes();
     expect(result.getCompileMode()).andReturn(mode).anyTimes();
-    expect(result.isDebug()).andReturn(false).anyTimes();
+    expect(result.isDebug()).andReturn(true).anyTimes();
     replay(result);
     return result;
   }
@@ -108,7 +108,7 @@ public class ExportJsCompilerTest {
     replay(result);
     return result;
   }
-
+  
   private FeatureResource mockResource(boolean external, String debContent, 
String optContent) {
     FeatureResource result = createMock(FeatureResource.class);
     expect(result.getDebugContent()).andReturn(debContent).anyTimes();

Added: 
shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompilerTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompilerTest.java?rev=1085647&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompilerTest.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/test/java16/org/apache/shindig/gadgets/rewrite/js/ClosureJsCompilerTest.java
 Sat Mar 26 05:18:14 2011
@@ -0,0 +1,238 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+package org.apache.shindig.gadgets.rewrite.js;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.EasyMock.replay;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.javascript.jscomp.Compiler;
+import com.google.javascript.jscomp.CompilerOptions;
+import com.google.javascript.jscomp.DiagnosticType;
+import com.google.javascript.jscomp.JSError;
+import com.google.javascript.jscomp.JSSourceFile;
+import com.google.javascript.jscomp.Result;
+
+import junit.framework.TestCase;
+
+import org.apache.shindig.common.cache.Cache;
+import org.apache.shindig.common.cache.CacheProvider;
+import org.apache.shindig.common.cache.NullCache;
+import org.apache.shindig.gadgets.JsCompileMode;
+import org.apache.shindig.gadgets.RenderingContext;
+import org.apache.shindig.gadgets.features.ApiDirective;
+import org.apache.shindig.gadgets.features.FeatureRegistry.FeatureBundle;
+import org.apache.shindig.gadgets.js.JsContent;
+import org.apache.shindig.gadgets.js.JsResponse;
+import org.apache.shindig.gadgets.rewrite.js.ExportJsCompiler;
+import org.apache.shindig.gadgets.uri.UriStatus;
+import org.apache.shindig.gadgets.uri.JsUriManager.JsUri;
+
+import java.util.List;
+
+public class ClosureJsCompilerTest extends TestCase {
+
+  private Compiler realCompMock;
+  private CompilerOptions realOptionsMock;
+  private Result realResultMock;
+  private ExportJsCompiler exportCompilerMock;
+  private JsResponse exportResponseMock;
+  private JsUri jsUriMock;
+  private CacheProvider cacheMock;
+  private ClosureJsCompiler compiler;
+
+  private final String ACTUAL_COMPILER_OUTPUT = "window.abc={};";
+  private final String EXPORT_COMPILER_STRING = "window['abc'] = {};";
+  private final Iterable<JsContent> EXPORT_COMPILER_CONTENTS =
+      newJsContents(EXPORT_COMPILER_STRING);
+
+  private final String CLOSURE_ACTUAL_COMPILER_OUTPUT = ACTUAL_COMPILER_OUTPUT;
+  private final String CLOSURE_EXPORT_COMPILER_OUTPUT = EXPORT_COMPILER_STRING;
+
+  private final List<String> EXPORTS = ImmutableList.of("foo", "bar");
+
+  private final String EXTERN = "extern";
+  private final String ERROR_NAME = "error";
+  private final JSError JS_ERROR = JSError.make(
+      "js", 12, 34, DiagnosticType.error(ERROR_NAME, "errDesc"));
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    cacheMock = new MockProvider();
+    exportResponseMock = mockJsResponse();
+    exportCompilerMock = mockExportJsCompiler(exportResponseMock);
+  }
+
+  public void testGetJsContentWithGoogSymbolExports() throws Exception {
+    realOptionsMock = mockRealJsCompilerOptions(true); // with
+    compiler = newClosureJsCompiler(null, realOptionsMock, exportCompilerMock, 
cacheMock);
+    FeatureBundle bundle = mockBundle(EXPORTS);
+    Iterable<JsContent> actual = compiler.getJsContent(mockJsUri(false), 
bundle);
+    assertEquals(EXPORT_COMPILER_STRING +
+        "goog.exportSymbol('bar', bar);\n" +
+        "goog.exportSymbol('foo', foo);\n",
+        getContent(actual));
+  }
+
+  public void testGetJsContentWithoutGoogSymbolExports() throws Exception {
+    realOptionsMock = mockRealJsCompilerOptions(false); // without
+    compiler = newClosureJsCompiler(null, realOptionsMock, exportCompilerMock, 
cacheMock);
+    FeatureBundle bundle = mockBundle(EXPORTS);
+    Iterable<JsContent> actual = compiler.getJsContent(mockJsUri(false), 
bundle);
+    assertEquals(EXPORT_COMPILER_STRING, getContent(actual));
+  }
+
+  public void testCompileSuccessOpt() throws Exception {
+    jsUriMock = mockJsUri(false); // opt
+    realResultMock = mockRealJsResult();
+    realCompMock = mockRealJsCompiler(null, realResultMock, 
ACTUAL_COMPILER_OUTPUT);
+    realOptionsMock = mockRealJsCompilerOptions(false);
+    compiler = newClosureJsCompiler(realCompMock, realOptionsMock, 
exportCompilerMock, cacheMock);
+    JsResponse actual = compiler.compile(jsUriMock, EXPORT_COMPILER_CONTENTS,
+        ImmutableList.of(EXTERN));
+    assertEquals(CLOSURE_ACTUAL_COMPILER_OUTPUT, actual.toJsString());
+    assertTrue(actual.getErrors().isEmpty());
+  }
+
+  public void testCompileSuccessDeb() throws Exception {
+    jsUriMock = mockJsUri(true); // debug
+    realResultMock = mockRealJsResult();
+    realCompMock = mockRealJsCompiler(null, realResultMock, 
ACTUAL_COMPILER_OUTPUT);
+    realOptionsMock = mockRealJsCompilerOptions(false);
+    compiler = newClosureJsCompiler(realCompMock, realOptionsMock, 
exportCompilerMock, cacheMock);
+    JsResponse actual = compiler.compile(jsUriMock, EXPORT_COMPILER_CONTENTS,
+        ImmutableList.of(EXTERN));
+    assertEquals(CLOSURE_EXPORT_COMPILER_OUTPUT, actual.toJsString());
+    assertTrue(actual.getErrors().isEmpty());
+  }
+
+  public void testCompileErrorOpt() throws Exception {
+    jsUriMock = mockJsUri(false); // opt
+    realCompMock = mockRealJsCompiler(JS_ERROR, realResultMock, 
ACTUAL_COMPILER_OUTPUT);
+    realOptionsMock = mockRealJsCompilerOptions(true); // force compiler to run
+    compiler = newClosureJsCompiler(realCompMock, realOptionsMock, 
exportCompilerMock, cacheMock);
+    JsResponse actual = compiler.compile(jsUriMock, EXPORT_COMPILER_CONTENTS,
+        ImmutableList.of(EXTERN));
+    assertTrue(actual.getErrors().get(0).contains(ERROR_NAME));
+    assertEquals(1, actual.getErrors().size());
+  }
+
+  public void testCompileErrorDeb() throws Exception {
+    jsUriMock = mockJsUri(true); // debug
+    realCompMock = mockRealJsCompiler(JS_ERROR, realResultMock, 
ACTUAL_COMPILER_OUTPUT);
+    realOptionsMock = mockRealJsCompilerOptions(true); // force compiler to run
+    compiler = newClosureJsCompiler(realCompMock, realOptionsMock, 
exportCompilerMock, cacheMock);
+    JsResponse actual = compiler.compile(jsUriMock, EXPORT_COMPILER_CONTENTS,
+        ImmutableList.of(EXTERN));
+    assertTrue(actual.getErrors().get(0).contains(ERROR_NAME));
+    assertEquals(1, actual.getErrors().size());
+  }
+
+  private ClosureJsCompiler newClosureJsCompiler(final Compiler realComp,
+      CompilerOptions realOptions, ExportJsCompiler exportComp, CacheProvider 
cache) {
+    return new ClosureJsCompiler(realOptionsMock, exportComp, cache) {
+      @Override
+      Compiler newCompiler() {
+        return realComp;
+      }
+    };
+  }
+
+  private JsResponse mockJsResponse() {
+    JsResponse result = createMock(JsResponse.class);
+    expect(result.toJsString()).andReturn(EXPORT_COMPILER_STRING).anyTimes();
+    
expect(result.getAllJsContent()).andReturn(EXPORT_COMPILER_CONTENTS).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  @SuppressWarnings("unchecked")
+  private ExportJsCompiler mockExportJsCompiler(JsResponse res) {
+    ExportJsCompiler result = createMock(ExportJsCompiler.class);
+    expect(result.getJsContent(isA(JsUri.class), isA(FeatureBundle.class)))
+        .andReturn(EXPORT_COMPILER_CONTENTS).anyTimes();
+    expect(result.compile(isA(JsUri.class), isA(Iterable.class), 
isA(List.class)))
+        .andReturn(res).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  private Result mockRealJsResult() {
+    Result result = createMock(Result.class);
+    return result;
+  }
+
+  private Compiler mockRealJsCompiler(JSError error, Result res, String 
toSource) {
+    Compiler result = createMock(Compiler.class);
+    expect(result.compile(isA(JSSourceFile[].class), isA(JSSourceFile[].class),
+        isA(CompilerOptions.class))).andReturn(res);
+    if (error != null) {
+      expect(result.hasErrors()).andReturn(true);
+      expect(result.getErrors()).andReturn(new JSError[] { error });
+    } else {
+      expect(result.hasErrors()).andReturn(false);
+    }
+    expect(result.getResult()).andReturn(res);
+    expect(result.toSource()).andReturn(toSource);
+    replay(result);
+    return result;
+  }
+
+  private CompilerOptions mockRealJsCompilerOptions(boolean 
enableExternExports) {
+    CompilerOptions result = createMock(CompilerOptions.class);
+    
expect(result.isExternExportsEnabled()).andReturn(enableExternExports).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  private JsUri mockJsUri(boolean debug) {
+    JsUri result = createMock(JsUri.class);
+    expect(result.isDebug()).andReturn(debug).anyTimes();
+    
expect(result.getCompileMode()).andReturn(JsCompileMode.ALL_RUN_TIME).anyTimes();
+    
expect(result.getStatus()).andReturn(UriStatus.VALID_UNVERSIONED).anyTimes();
+    expect(result.getContainer()).andReturn("container").anyTimes();
+    
expect(result.getContext()).andReturn(RenderingContext.CONFIGURED_GADGET).anyTimes();
+    expect(result.getRefresh()).andReturn(1000).anyTimes();
+    expect(result.isNoCache()).andReturn(false).anyTimes();
+    expect(result.getGadget()).andReturn("http://foo.com/g.xml";).anyTimes();
+    expect(result.getLibs()).andReturn(ImmutableList.<String>of()).anyTimes();
+    
expect(result.getLoadedLibs()).andReturn(ImmutableList.<String>of()).anyTimes();
+    expect(result.getOnload()).andReturn("foo").anyTimes();
+    expect(result.isJsload()).andReturn(true).anyTimes();
+    expect(result.isNohint()).andReturn(true).anyTimes();
+    expect(result.getOrigUri()).andReturn(null).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  private FeatureBundle mockBundle(List<String> exports) {
+    FeatureBundle result = createMock(FeatureBundle.class);
+    expect(result.getApis(ApiDirective.Type.JS, 
true)).andReturn(exports).anyTimes();
+    expect(result.getName()).andReturn(null).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  private class MockProvider implements CacheProvider {
+    public <K, V> Cache<K, V> createCache(String name) {
+      return new NullCache<K, V>();
+    }
+  }
+
+  private String getContent(Iterable<JsContent> jsContent) {
+    StringBuilder sb = new StringBuilder();
+    for (JsContent js : jsContent) {
+      sb.append(js.get());
+    }
+    return sb.toString();
+  }
+
+  private static List<JsContent> newJsContents(String jsCode) {
+    List<JsContent> result = Lists.newArrayList();
+    result.add(new JsContent(jsCode, null));
+    return result;
+  }
+}

Modified: shindig/trunk/pom.xml
URL: 
http://svn.apache.org/viewvc/shindig/trunk/pom.xml?rev=1085647&r1=1085646&r2=1085647&view=diff
==============================================================================
--- shindig/trunk/pom.xml (original)
+++ shindig/trunk/pom.xml Sat Mar 26 05:18:14 2011
@@ -1364,6 +1364,10 @@
       <id>oauth</id>
       <url>http://oauth.googlecode.com/svn/code/maven</url>
     </repository>
+    <repository>
+      <id>com.google.javascript</id>
+      <url>http://oss.sonatype.org/content/groups/staging</url>
+    </repository>
     <!-- for jstl-1.2 for now.. -->
     <repository>
       <id>java.net</id>


Reply via email to