]> source.dussan.org Git - gwtquery.git/commitdiff
Implementation of JsniBundles, a generator which allows jsni methods which have javas...
authorManuel Carrasco Moñino <manuel.carrasco.m@gmail.com>
Sat, 16 Nov 2013 17:49:51 +0000 (18:49 +0100)
committerManuel Carrasco Moñino <manuel.carrasco.m@gmail.com>
Sat, 16 Nov 2013 17:49:51 +0000 (18:49 +0100)
gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml
gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml
gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java [new file with mode: 0644]
gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java [new file with mode: 0644]

index 873e09ab5873039f752fc25e1e6bd92a3dfe9df8..a902fd99dba30b217dee718c3b03091d2625d869 100644 (file)
         <when-type-assignable class="com.google.gwt.query.client.Browser"/>
     </generate-with>
 
+    <!-- JSNI Generator -->
+    <generate-with class="com.google.gwt.query.rebind.JsniBundleGenerator">
+        <when-type-assignable class="com.google.gwt.query.client.builders.JsniBundle"/>
+    </generate-with>
+
     <!-- Json and Xml builders -->
     <generate-with class="com.google.gwt.query.rebind.JsonBuilderGenerator">
         <when-type-assignable class="com.google.gwt.query.client.builders.JsonBuilder"/>
index 385adde85bf0374350d2de06aaa196a1f40d747c..c67ec2af400465803fddc303611214eb5fab7d86 100644 (file)
         </any>
     </set-property>
     
+    <!-- JSNI Generator -->
+    <generate-with class="com.google.gwt.query.rebind.JsniBundleGenerator">
+        <when-type-assignable class="com.google.gwt.query.client.builders.JsniBundle"/>
+    </generate-with>    
+
     <!-- Browser flags -->
     <generate-with class="com.google.gwt.query.rebind.BrowserGenerator">
         <when-type-assignable class="com.google.gwt.query.client.Browser"/>
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java
new file mode 100644 (file)
index 0000000..5623f4e
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2013, The gwtquery team.
+ *
+ * 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.query.client.builders;
+
+import static java.lang.annotation.ElementType.*;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A tag interface that is used in the generation of jsni bundles. 
+ * 
+ * A jsni-bundle is a class with jsni methods whose content is taken from
+ * external handwritten javascript files.
+ * 
+ * The goals of using this technique are:
+ *   - Use pure javascript files so as we can use IDEs for editing, formating etc.
+ *   - Ability to test js files directly in the browser without compiling.
+ *   - Include 3party libraries without modification of the original sources.
+ *   - Not need of including javascript tags to include 3party js.
+ *   - GWT compiler will get rid of these jsni fragments if the application
+ *     does not use any method.
+ *   - Included javascript will take advantage of GWT jsni validators, obfuscators
+ *     and optimizers.
+ * <pre>
+    public interface HighCharts extends JsniBundle {
+      @LibrarySource("highcharts.js")
+      public void initialize();
+
+      @MethodSource("createChart.js")
+      public JavaScriptObject createChart(String elementId, JavaScriptObject config);
+   }
+
+   // Generate the Bundle implementation
+   HighCharts library = GWT.create(HighCharts.class);
+
+   // Initialize the 3party library
+   library.initialize();
+
+   // Create a new chart
+   JavaScriptObject chart = hc.createChart("container", properties);
+
+ * </pre>
+ */
+public interface JsniBundle {
+  
+  /**
+   * Annotation to mark inclusion of 3 party libraries.
+   *  
+   * The content is wrapped with an inner javascript function to set
+   * the context to the document window instead of the iframe where GWT
+   * code is run. 
+   */
+  @Target({METHOD})
+  @Retention(RetentionPolicy.RUNTIME)
+  @Documented
+  public @interface LibrarySource {
+    /**
+     * The external file to be included
+     */
+    String value();
+
+    /**
+     * Fragment of code to include before the external javascript is
+     * written.
+     */
+    String prepend() default "(function(window, document, console){\n";
+
+    /**
+     * Fragment of code to include after the external javascript has been
+     * written.
+     */
+    String postpend() default "\n}.apply($wnd, [$wnd, $doc, $wnd.console]));"; 
+  }
+  
+  /**
+   * Annotation to mark inclusion of jsni code writen in external js files.
+   *  
+   * The content is not wrapped by default, so the developer has the responsibility
+   * to return the suitable value and to handle correctly parameters.
+   */
+  @Target({METHOD})
+  @Retention(RetentionPolicy.RUNTIME)
+  @Documented
+  public @interface MethodSource {
+    /**
+     * The external file to be included
+     */
+    String value();
+
+    /**
+     * Fragment of code to include before the external javascript is
+     * written.
+     */
+    String prepend() default "";
+
+    /**
+     * Fragment of code to include after the external javascript has been
+     * written.
+     */
+    String postpend() default ""; 
+  }
+}
diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java
new file mode 100644 (file)
index 0000000..8328d81
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013, The gwtquery team.
+ *
+ * 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.query.rebind;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.query.client.builders.JsniBundle;
+import com.google.gwt.query.client.builders.JsniBundle.LibrarySource;
+import com.google.gwt.query.client.builders.JsniBundle.MethodSource;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+/**
+ * Generates an implementation of a user-defined interface <code>T</code> that
+ * extends {@link JsniBundle}.
+ * 
+ * The generated implementation includes hand-written external js-files into
+ * jsni methods so as those files can take advantage of gwt compiler optimizations.
+ * 
+ */
+public class JsniBundleGenerator extends Generator {
+
+  public String generate(TreeLogger logger,
+      GeneratorContext context, String requestedClass)
+      throws UnableToCompleteException {
+    
+    TypeOracle oracle = context.getTypeOracle();
+    JClassType clazz = oracle.findType(requestedClass);
+
+    String packageName = clazz.getPackage().getName();
+    String className = clazz.getName().replace('.', '_') + "_Impl" ;
+    String fullName = packageName + "." + className;
+    
+    PrintWriter pw = context.tryCreate(logger, packageName, className);
+    
+    if (pw != null) {
+
+      ClassSourceFileComposerFactory fact = new ClassSourceFileComposerFactory(packageName, className);
+      fact.addImplementedInterface(requestedClass);
+      SourceWriter sw = fact.createSourceWriter(context, pw);
+      
+      if (sw != null) {
+        for (JMethod method : clazz.getMethods()) {
+          LibrarySource librarySource = method.getAnnotation(LibrarySource.class);
+          String value, prepend, postpend;
+          if (librarySource != null) {
+            value = librarySource.value();
+            prepend = librarySource.prepend();
+            postpend = librarySource.postpend();
+          } else {
+            MethodSource methodSource = method.getAnnotation(MethodSource.class);
+            if (methodSource != null) {
+              value = methodSource.value();
+              prepend = methodSource.prepend();
+              postpend = methodSource.postpend();
+            } else {
+              return null;
+            }
+          }
+          String file = packageName.replace(".", "/") + "/" + value;
+          try {
+            InputStream is = this.getClass().getClassLoader().getResourceAsStream(file);
+            OutputStream os = new ByteArrayOutputStream();
+            IOUtils.copy(is, os);
+            
+            String jsni = os.toString()
+                // remove MS <CR>
+                .replace("\r", "")
+                // remove 'c' (/* */) style comments blocks 
+                .replaceAll("(\\s*/\\*[\\s\\S]*?\\*/\\s*)", "\n")
+                // remove 'c++' (//) style comment lines
+                .replaceAll("(?m)^\\s*//.*$", "")
+                // remove 'c++' (//) style comments at the end of a code line
+                .replaceAll("(?m)^(.*)//[^'\"]*?$", "$1")
+                // remove empty lines
+                .replaceAll("\n+", "\n");
+                ;
+            
+            // Using pw instead of sw in order to avoid stack errors because sw.print is a recursive function
+            // and it fails with very long javascript files.
+                
+            // JMethod.toString() prints the java signature of the method, so we just have to replace abstract by native.    
+            pw.println(method.toString().replace("abstract", "native") + "/*-{");
+            pw.println(prepend);
+            pw.println(jsni);
+            pw.println(postpend);
+            pw.println("}-*/;");
+          } catch (Exception e) {
+            e.printStackTrace();
+            throw new UnableToCompleteException();
+          }
+        }
+        
+        sw.commit(logger);
+      }
+    }
+
+    return fullName;
+  }
+}