Author: mhermanto
Date: Thu Feb 24 04:28:51 2011
New Revision: 1074040

URL: http://svn.apache.org/viewvc?rev=1074040&view=rev
Log:
Improve JS export mechanism to enable property renaming, in JS runtime 
compilation.
http://codereview.appspot.com/4175057/

Added:
    shindig/trunk/features/src/main/javascript/features/exportjs/
    shindig/trunk/features/src/main/javascript/features/exportjs/exportjs.js
    shindig/trunk/features/src/main/javascript/features/exportjs/feature.xml
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/
    
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompilerTest.java
Modified:
    shindig/trunk/features/src/main/javascript/features/features.txt
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompiler.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/JsCompiler.java
    
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsHandler.java

Added: shindig/trunk/features/src/main/javascript/features/exportjs/exportjs.js
URL: 
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/exportjs/exportjs.js?rev=1074040&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/exportjs/exportjs.js 
(added)
+++ shindig/trunk/features/src/main/javascript/features/exportjs/exportjs.js 
Thu Feb 24 04:28:51 2011
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+/**
+ * @fileoverview This provides a mechanism to export symbols, across all
+ * cases below. This implementation works best when used with 
ClosureJsCompiler.
+ *
+ * "global" object, ie: feature=globals.
+ * feature directive: <exports type="js">gadgets</exports>
+ * code: var gadgets = {};
+ *
+ * "singleton" object, ie: feature=rpc.
+ * feature directive: <exports type="js">gadgets.foo.bar</exports>
+ * gadgets.foo = function() {
+ *   return { bar : function() { ... } };
+ * }();
+ *
+ * "closured" object, ie: feature=shindig.uri.
+ * This wraps to a function that exports any resulting properties it returns
+ * in an Object. feature directive: <exports 
type="js">gadgets.foo.bar</exports>
+ * gadgets.foo = (function() {
+ *   return { bar : function() { ... } };
+ * })();
+ *
+ * "prototype" object, ie: feature=container.
+ * feature directive: <exports type="js">gadgets.foo.prototype.bar</exports>
+ * gadgets.foo = function() {};
+ * gadgets.foo.prototype.bar = function() { ... };
+ *
+ * "static" object.
+ * feature directive: <exports type="js">gadgets.foo.bar</exports>
+ * gadgets.foo = {};
+ * gadgets.foo.bar = function() { ... };
+ */
+function exportJs(namespace, components, opt_props) {
+  var base = window;
+  var prevBase = null;
+  var nsParts = namespace.split('.');
+
+  for (var i = 0, part; part = nsParts.shift(); i++) {
+    base[part] = components[i] || {};
+    prevBase = base;
+    base = base[part];
+  }
+
+  var exportProps = function(root) {
+    var props = opt_props || {};
+    for (var prop in props) {
+      if (props.hasOwnProperty(prop) && root.hasOwnProperty(prop)) {
+        root[props[prop]] = root[prop];
+      }
+    }
+  };
+
+  if (typeof base === 'object') {
+    exportProps(base);
+
+  } else if (typeof base === 'function') {
+    var exportedFn = function() {
+      var result = base.apply(null, arguments);
+      if (typeof result === 'object') {
+        exportProps(result);
+      }
+      return result;
+    };
+    prevBase[part] = exportedFn;
+  }
+}

Added: shindig/trunk/features/src/main/javascript/features/exportjs/feature.xml
URL: 
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/exportjs/feature.xml?rev=1074040&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/exportjs/feature.xml 
(added)
+++ shindig/trunk/features/src/main/javascript/features/exportjs/feature.xml 
Thu Feb 24 04:28:51 2011
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!--
+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.
+-->
+<feature>
+  <name>exportjs</name>
+  <gadget>
+    <script src="exportjs.js"/>
+  </gadget>
+  <container>
+    <script src="exportjs.js"/>
+  </container>
+</feature>

Modified: shindig/trunk/features/src/main/javascript/features/features.txt
URL: 
http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/features.txt?rev=1074040&r1=1074039&r2=1074040&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/features.txt (original)
+++ shindig/trunk/features/src/main/javascript/features/features.txt Thu Feb 24 
04:28:51 2011
@@ -39,6 +39,7 @@ features/core/feature.xml
 features/dynamic-height.height/feature.xml
 features/dynamic-height.util/feature.xml
 features/dynamic-height/feature.xml
+features/exportjs/feature.xml
 features/flash/feature.xml
 features/i18n/feature.xml
 features/jsondom/feature.xml

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompiler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompiler.java?rev=1074040&r1=1074039&r2=1074040&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompiler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompiler.java
 Thu Feb 24 04:28:51 2011
@@ -17,43 +17,155 @@
  */
 package org.apache.shindig.gadgets.rewrite.js;
 
-import com.google.caja.util.Sets;
+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 org.apache.commons.lang.StringUtils;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.features.FeatureRegistry;
+import org.apache.shindig.gadgets.features.FeatureRegistry.LookupResult;
+import org.apache.shindig.gadgets.features.FeatureResource;
 
-import java.util.Collections;
+import java.util.Collection;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 
 /**
  * Base for a JsCompiler implementation.
  */
 public abstract class AbstractJsCompiler implements JsCompiler {
 
-  public String generateExportStatements(List<String> symbols) {
-    StringBuilder exportStatements = new StringBuilder();
-    Set<String> allExports = Sets.newHashSet();
-    List<String> exports = Lists.newArrayListWithCapacity(symbols.size());
-    exports.addAll(symbols);
-    Collections.sort(exports);
-    String prevExport = null;
-    for (String export : exports) {
-      if (!export.equals(prevExport)) {
-        String[] pieces = StringUtils.split(export, "\\.");
-        String base = "window";
-        for (int i = 0; i < pieces.length; ++i) {
-          String symExported = (i == 0) ? pieces[0] : base + "." + pieces[i];
-          if (!allExports.contains(symExported)) {
-            String curExport = base + "['" + pieces[i] + "']=" + symExported + 
";\n";
-            exportStatements.append(curExport);
-            allExports.add(symExported);
-          }
-          base = symExported;
-        }
+  @VisibleForTesting
+  static final String FEATURE_NAME = "exportjs";
+
+  private static final String FUNCTION_NAME = "exportJs";
+
+  private boolean hasInjectedJs = false;
+  private final FeatureRegistry featureRegistry;
+
+  @Inject
+  public AbstractJsCompiler(FeatureRegistry featureRegistry) {
+    this.featureRegistry = featureRegistry;
+  }
+
+  private void appendExportJsFeature(StringBuilder out, GadgetContext context) 
{
+    LookupResult lookup = featureRegistry.getFeatureResources(context,
+        ImmutableList.of(FEATURE_NAME), null);
+    for (FeatureRegistry.FeatureBundle bundle : lookup.getBundles()) {
+      for (FeatureResource resource : bundle.getResources()) {
+        out.append(resource.getDebugContent());
+      }
+    }
+  }
+
+  public String generateExportStatements(GadgetContext context, List<String> 
symbols) {
+    Collection<Input> inputs = generateInputs(symbols);
+    StringBuilder result = new StringBuilder();
+    if (!inputs.isEmpty()) {
+      if (!hasInjectedJs) {
+        appendExportJsFeature(result, context);
+        hasInjectedJs = true;
       }
-      prevExport = export;
+      for (Input input : inputs) {
+        result.append(generateExportStatement(input));
+      }
+    }
+    return result.toString();
+  }
+
+  private static class Input {
+    String namespace;
+    List<String> components;
+    List<String> properties;
+    private Input(String namespace, List<String> components) {
+      this.namespace = namespace;
+      this.components = components;
+      this.properties = Lists.newArrayList();
+    }
+    static Input newGlobal() {
+      return new Input(null, ImmutableList.<String>of());
     }
-    return exportStatements.toString();
+    static Input newLocal(String namespace, List<String> components) {
+      return new Input(namespace, components);
+    }
+  }
+
+  private List<String> expandNamespace(String namespace) {
+    List<String> result = Lists.newArrayList();
+    for (int from = 0; ;) {
+      int idx = namespace.indexOf('.', from);
+      if (idx >= 0) {
+        result.add(namespace.substring(0, idx));
+        from = idx + 1;
+      } else {
+        result.add(namespace);
+        break;
+      }
+    }
+    return result;
+  }
+
+  private Collection<Input> generateInputs(List<String> symbols) {
+    Map<String, Input> result = Maps.newHashMap();
+    for (String symbol : symbols) {
+      String ns = getNamespace(symbol);
+      Input input = result.get(ns);
+      if (input == null) {
+        input = (ns != null) ? Input.newLocal(ns, expandNamespace(ns)) : 
Input.newGlobal();
+        result.put(ns, input);
+      }
+      String property = (ns != null) ? getProperty(symbol) : symbol;
+      input.properties.add(property);
+    }
+    return result.values();
+  }
+
+  private String generateExportStatement(Input input) {
+    StringBuilder result = new StringBuilder();
+
+    // Local namespace.
+    if (input.namespace != null) {
+      
result.append(FUNCTION_NAME).append("('").append(input.namespace).append("',[");
+      result.append(Joiner.on(',').join(input.components));
+      result.append("],{");
+      for (int i = 0; i < input.properties.size(); i++) {
+        String prop = input.properties.get(i);
+        if (i > 0) result.append(",");
+        result.append(prop).append(":'").append(prop).append("'");
+      }
+      result.append("});");
+
+    // Global/window namespace.
+    } else {
+      for (int i = 0; i < input.properties.size(); i++) {
+        String prop = input.properties.get(i);
+        result.append(FUNCTION_NAME).append("(");
+        result.append("'").append(prop).append("',[").append(prop);
+        result.append("]);");
+      }
+    }
+
+    return result.toString();
+  }
+
+  /**
+   * Return the namespace for symbol (before last dot). If symbol is global,
+   * return null, to indicate "window" namespace.
+   */
+  private String getNamespace(String symbol) {
+    int idx = symbol.lastIndexOf('.');
+    return (idx >= 0) ? symbol.substring(0, idx) : null;
+  }
+
+  /**
+   * Return the property of symbol (after last dot). If symbol is global,
+   * return the original string.
+   */
+  private String getProperty(String symbol) {
+    int idx = symbol.lastIndexOf('.');
+    return (idx >= 0) ? symbol.substring(idx + 1) : symbol;
   }
 }

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/JsCompiler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/JsCompiler.java?rev=1074040&r1=1074039&r2=1074040&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/JsCompiler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/js/JsCompiler.java
 Thu Feb 24 04:28:51 2011
@@ -17,45 +17,48 @@
  */
 package org.apache.shindig.gadgets.rewrite.js;
 
+import org.apache.shindig.gadgets.GadgetContext;
+
 import java.util.List;
 
 public interface JsCompiler {
   /**
    * Compiles the provided code with the provided list of external symbols.
-   * 
+   *
    * @param jsData The code to compile.
    * @param externs The list of external symbols.
    * @return A compilation result object.
    */
   public Result compile(String jsData, List<String> externs);
-  
+
   /**
    * Generates a sequence of statements to mark the specified symbols as
    * exported.
    *
+   * @param context The gadget context.
    * @param symbols The symbols to export.
    * @return A sequence of JavaScript statements to export those symbols.
    */
-  public String generateExportStatements(List<String> symbols);
-  
+  public String generateExportStatements(GadgetContext context, List<String> 
symbols);
+
   public static class Result {
     private final String compiled;
     private final List<String> errors;
-    
+
     public Result(String compiled) {
       this.compiled = compiled;
       this.errors = null;
     }
-    
+
     public Result(List<String> errors) {
       this.compiled = null;
       this.errors = errors;
     }
-    
+
     public String getCode() {
       return compiled;
     }
-    
+
     public List<String> getErrors() {
       return errors;
     }

Modified: 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsHandler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsHandler.java?rev=1074040&r1=1074039&r2=1074040&view=diff
==============================================================================
--- 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsHandler.java
 (original)
+++ 
shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsHandler.java
 Thu Feb 24 04:28:51 2011
@@ -136,7 +136,7 @@ public class JsHandler {
         // Add externs for this bundle.
         appendExternsForBundle(bundle, allExterns);
 
-        jsData.append(compiler.generateExportStatements(rawExports));
+        jsData.append(compiler.generateExportStatements(ctx, rawExports));
       }
     }
 

Added: 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompilerTest.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompilerTest.java?rev=1074040&view=auto
==============================================================================
--- 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompilerTest.java
 (added)
+++ 
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/rewrite/js/AbstractJsCompilerTest.java
 Thu Feb 24 04:28:51 2011
@@ -0,0 +1,110 @@
+/*
+ * 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 static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.features.FeatureRegistry;
+import org.apache.shindig.gadgets.features.FeatureRegistry.LookupResult;
+import org.apache.shindig.gadgets.features.FeatureResource;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class AbstractJsCompilerTest {
+  private final String EXPORT_JS_FUNCTION = "function exportJs() { };";
+
+  private AbstractJsCompiler compiler;
+  private GadgetContext context;
+
+  @Before
+  public void setUp() throws Exception {
+    context = new GadgetContext();
+    FeatureResource featureResourceMock = mockFeatureResource();
+    FeatureRegistry.FeatureBundle featureBundle = new 
FeatureRegistry.FeatureBundle(
+        null, ImmutableList.of(featureResourceMock));
+    LookupResult lookupMock = mockLookupResult(featureBundle);
+    FeatureRegistry featureRegistryMock = mockFeatureRegistry(lookupMock);
+
+    compiler = new AbstractJsCompiler(featureRegistryMock) {
+      public Result compile(String jsData, List<String> externs) {
+        return null;
+      }
+    };
+  }
+
+  @SuppressWarnings("unchecked")
+  private FeatureRegistry mockFeatureRegistry(LookupResult lookupMock) {
+    FeatureRegistry result = createMock(FeatureRegistry.class);
+    expect(result.getFeatureResources(
+        context, ImmutableList.of(AbstractJsCompiler.FEATURE_NAME), null)).
+        andReturn(lookupMock).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  private LookupResult mockLookupResult(FeatureRegistry.FeatureBundle 
featureBundle) {
+    LookupResult result = createMock(LookupResult.class);
+    
expect(result.getBundles()).andReturn(ImmutableList.of(featureBundle)).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  private FeatureResource mockFeatureResource() {
+    FeatureResource result = createMock(FeatureResource.class);
+    expect(result.getDebugContent()).andReturn(EXPORT_JS_FUNCTION).anyTimes();
+    replay(result);
+    return result;
+  }
+
+  @Test
+  public void testGenerateExportStatementsEmpty() throws Exception {
+    String actual = compiler.generateExportStatements(context, 
ImmutableList.<String>of());
+    assertEquals("", actual);
+  }
+
+  @Test
+  public void testGenerateExportStatementsNotEmpty() throws Exception {
+    ImmutableList<String> list = ImmutableList.of(
+        "gadgets",
+        "gadgets.rpc.call",
+        "cc",
+        "cc.prototype.site");
+    String actual = compiler.generateExportStatements(context, list);
+    assertEquals(
+        EXPORT_JS_FUNCTION +
+        "exportJs('gadgets',[gadgets]);" +
+        "exportJs('cc',[cc]);" +
+        "exportJs('gadgets.rpc',[gadgets,gadgets.rpc],{call:'call'});" +
+        "exportJs('cc.prototype',[cc,cc.prototype],{site:'site'});",
+        actual);
+    list = ImmutableList.of("gadgets.util.makeClosure");
+    actual = compiler.generateExportStatements(context, list);
+    assertEquals(
+        // JS functions only get inlined once.
+        
"exportJs('gadgets.util',[gadgets,gadgets.util],{makeClosure:'makeClosure'});",
+        actual);
+  }
+}


Reply via email to