com.google.gwt.query.rebind.JsniBundleGenerator Maven / Gradle / Ivy
The newest version!
/*
* 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 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);
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();
}
}