From 7425ebf906a878e061cba0a08525ed217f2977c0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Manuel=20Carrasco=20Mo=C3=B1ino?= Date: Sat, 16 Nov 2013 18:49:51 +0100 Subject: [PATCH] Implementation of JsniBundles, a generator which allows jsni methods which have javascript in external files. --- .../java/com/google/gwt/query/Query.gwt.xml | 5 + .../com/google/gwt/query/QueryMin.gwt.xml | 5 + .../gwt/query/client/builders/JsniBundle.java | 118 +++++++++++++++++ .../gwt/query/rebind/JsniBundleGenerator.java | 125 ++++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java create mode 100644 gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml index 873e09ab..a902fd99 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml +++ b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml @@ -22,6 +22,11 @@ + + + + + diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml b/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml index 385adde8..c67ec2af 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml +++ b/gwtquery-core/src/main/java/com/google/gwt/query/QueryMin.gwt.xml @@ -42,6 +42,11 @@ + + + + + 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 index 00000000..5623f4ed --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/builders/JsniBundle.java @@ -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. + *
+    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);
+
+ * 
+ */ +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 index 00000000..8328d811 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/rebind/JsniBundleGenerator.java @@ -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 T 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 + .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; + } +} -- 2.39.5