123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- /*
- * 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 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.LibrarySource;
- import com.google.gwt.query.client.builders.JsniBundle.MethodSource;
- import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
- import com.google.gwt.user.rebind.SourceWriter;
-
- import org.apache.commons.io.output.ByteArrayOutputStream;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.PrintWriter;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.util.zip.GZIPInputStream;
- import java.util.zip.InflaterInputStream;
-
- /**
- * 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);
- if (clazz.isInterface() != null) {
- fact.addImplementedInterface(requestedClass);
- } else {
- fact.setSuperclass(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;
- String replace[];
- if (librarySource != null) {
- value = librarySource.value();
- prepend = librarySource.prepend();
- postpend = librarySource.postpend();
- replace = librarySource.replace();
- } else {
- MethodSource methodSource = method.getAnnotation(MethodSource.class);
- if (methodSource != null) {
- value = methodSource.value();
- prepend = methodSource.prepend();
- postpend = methodSource.postpend();
- replace = methodSource.replace();
- } else {
- continue;
- }
- }
- try {
- // Read the javascript content
- String content = getContent(logger, packageName.replace(".", "/"), value);
-
- // Adjust javascript so as we can introduce it in a JSNI comment block without
- // breaking java syntax.
- String jsni = parseJavascriptSource(content);
-
- for (int i = 0; i < replace.length - 1; i += 2) {
- jsni = jsni.replaceAll(replace[i], replace[i + 1]);
- }
-
- pw.println(method.toString().replace("abstract", "native") + "/*-{");
- pw.println(prepend);
- pw.println(jsni);
- pw.println(postpend);
- pw.println("}-*/;");
- } catch (Exception e) {
- logger.log(TreeLogger.ERROR, "Error parsing javascript source: " + value + " "
- + e.getMessage());
- throw new UnableToCompleteException();
- }
- }
- }
- sw.commit(logger);
- }
-
- return fullName;
- }
-
- /**
- * Get the content of a javascript source. It supports remote sources hosted in CDN's.
- */
- private String getContent(TreeLogger logger, String path, String src)
- throws UnableToCompleteException {
- HttpURLConnection connection = null;
- InputStream in = null;
- try {
- if (!src.matches("(?i)https?://.*")) {
- String file = path + "/" + src;
- in = this.getClass().getClassLoader().getResourceAsStream(file);
- if (in == null) {
- // If we didn't find the resource relative to the package, assume it is absolute.
- file = src;
- in = this.getClass().getClassLoader().getResourceAsStream(file);
- }
- if (in != null) {
- logger.log(TreeLogger.INFO, getClass().getSimpleName()
- + " - importing external javascript: " + file);
- } else {
- logger.log(TreeLogger.ERROR, "Unable to read javascript file: " + file);
- }
- } else {
- logger.log(TreeLogger.INFO, getClass().getSimpleName()
- + " - downloading external javascript: " + src);
- URL url = new URL(src);
- connection = (HttpURLConnection) url.openConnection();
- connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
- connection.setRequestProperty("Host", url.getHost());
- connection.setConnectTimeout(3000);
- connection.setReadTimeout(3000);
-
- int status = connection.getResponseCode();
- if (status != HttpURLConnection.HTTP_OK) {
- logger.log(TreeLogger.ERROR, "Server Error: " + status + " "
- + connection.getResponseMessage());
- throw new UnableToCompleteException();
- }
-
- String encoding = connection.getContentEncoding();
- in = connection.getInputStream();
- if ("gzip".equalsIgnoreCase(encoding)) {
- in = new GZIPInputStream(in);
- } else if ("deflate".equalsIgnoreCase(encoding)) {
- in = new InflaterInputStream(in);
- }
- }
-
- return inputStreamToString(in);
- } catch (IOException e) {
- logger.log(TreeLogger.ERROR, "Error: " + e.getMessage());
- throw new UnableToCompleteException();
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- /**
- * Adapt a java-script block which could produce a syntax error when
- * embedding it in a JSNI block.
- *
- * The objective is to replace any 'c' comment-ending occurrence to avoid closing
- * JSNI comment blocks prematurely.
- *
- * A Regexp based parser is not reliable, this approach is better and faster.
- */
- // Note: this comment is intentionally using c++ style to allow writing the '*/' sequence.
- //
- // - Remove C comments: /* ... */
- // - Remove C++ comments: // ...
- // - Escape certain strings: '...*/...' to '...*' + '/...'
- // - Rewrite inline regex: /...*/igm to new RegExp('...*' + 'igm');
- private String parseJavascriptSource(String js) throws Exception {
-
- boolean isJS = true;
- boolean isSingQuot = false;
- boolean isDblQuot = false;
- boolean isSlash = false;
- boolean isCComment = false;
- boolean isCPPComment = false;
- boolean isRegex = false;
- boolean isOper = false;
-
- StringBuilder ret = new StringBuilder();
- String tmp = "";
- Character last = 0;
- Character prev = 0;
-
- for (int i = 0, l = js.length(); i < l; i++) {
- Character c = js.charAt(i);
- String out = c.toString();
-
- if (isJS) {
- isDblQuot = c == '"';
- isSingQuot = c == '\'';
- isSlash = c == '/';
- isJS = !isDblQuot && !isSingQuot && !isSlash;
- if (!isJS) {
- out = tmp = "";
- isCPPComment = isCComment = isRegex = false;
- }
- } else if (isSingQuot) {
- isJS = !(isSingQuot = last == '\\' || c != '\'');
- if (isJS)
- out = escapeQuotedString(tmp, c);
- else
- tmp += c;
- } else if (isDblQuot) {
- isJS = !(isDblQuot = last == '\\' || c != '"');
- if (isJS)
- out = escapeQuotedString(tmp, c);
- else
- tmp += c;
- } else if (isSlash) {
- if (!isCPPComment && !isCComment && !isRegex && !isOper) {
- isCPPComment = c == '/';
- isCComment = c == '*';
- isOper = !isCPPComment && !isCComment && !"=(&|?:;},".contains("" + prev);
- isRegex = !isCPPComment && !isCComment && !isOper;
- }
- if (isOper) {
- isJS = !(isSlash = isOper = false);
- out = "" + last + c;
- } else if (isCPPComment) {
- isJS = !(isSlash = isCPPComment = c != '\n');
- if (isJS)
- out = "\n";
- } else if (isCComment) {
- isSlash = isCComment = !(isJS = last == '*' && c == '/');
- if (isJS)
- out = "";
- } else if (isRegex) {
- isJS = !(isSlash = isRegex = last == '\\' || c != '/');
- if (isJS) {
- String mod = "";
- while (++i < l) {
- c = js.charAt(i);
- if ("igm".contains("" + c))
- mod += c;
- else
- break;
- }
- out = escapeInlineRegex(tmp, mod) + c;
- } else {
- tmp += c;
- }
- } else {
- isJS = true;
- }
- }
-
- if (isJS) {
- ret.append(out);
- }
- if (last != ' ') {
- prev = last;
- }
- last = prev == '\\' && c == '\\' ? 0 : c;
- }
- return ret.toString();
- }
-
- private String escapeQuotedString(String s, Character quote) {
- return quote + s.replace("*/", "*" + quote + " + " + quote + "/") + quote;
- }
-
- private String escapeInlineRegex(String s, String mod) {
- if (s.endsWith("*")) {
- return "new RegExp('" + s.replace("\\", "\\\\") + "','" + mod + "')";
- } else {
- return '/' + s + '/' + mod;
- }
- }
-
- private String inputStreamToString(InputStream in) throws IOException {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- byte[] buffer = new byte[4096];
- int read = in.read(buffer);
- while (read != -1) {
- bytes.write(buffer, 0, read);
- read = in.read(buffer);
- }
- in.close();
- return bytes.toString();
- }
- }
|