--- /dev/null
+/*
+ * 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 "";
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}