Skip to content

Commit

Permalink
Simple JavascriptResource implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
justinsb committed Jun 1, 2012
1 parent f9fb0bf commit 5b504b9
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 2 deletions.
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
*.class
# Eclipse files
.classpath
.project
.settings

#Maven stuff
target/

# Package Files #
# Generic java stuff
*.class
*.jar
*.war
*.ear
74 changes: 74 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.fathomdb</groupId>
<version>1.0-SNAPSHOT</version>

<artifactId>gwt-utils</artifactId>
<packaging>jar</packaging>
<name>GWT Utilities</name>

<properties>
<gwtVersion>2.4.0</gwtVersion>
<closureVersion>r1918</closureVersion>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwtVersion}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-gwt</artifactId>
<version>12.0-rc2</version>
</dependency>

<!-- For Javascript resource generation -->
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-dev</artifactId>
<version>${gwtVersion}</version>
</dependency>
<dependency>
<groupId>com.google.javascript</groupId>
<artifactId>closure-compiler</artifactId>
<version>${closureVersion}</version>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/java</directory>
</resource>
</resources>

<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>${gwtVersion}</version>
<executions>
<execution>
<!-- <goals> <goal>compile</goal> </goals> -->
</execution>
</executions>
<configuration>
<module>com.fathomdb.gwt.utils.GwtUtils</module>
<runTarget>GwtUtils.html</runTarget>
<hostedWebapp>${webappDirectory}</hostedWebapp>
</configuration>
</plugin>
</plugins>

</build>


</project>
6 changes: 6 additions & 0 deletions src/main/java/com/fathomdb/gwt/utils/GwtUtils.gwt.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='GwtUtils'>
<inherits name='com.google.gwt.user.User' />

<source path='resources' />
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.fathomdb.gwt.utils.resources;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.HeadElement;
import com.google.gwt.dom.client.ScriptElement;

public class JavascriptInjector {

private static HeadElement head;

public static void inject(String javascript) {
HeadElement head = getHead();
ScriptElement element = createScriptElement();
element.setText(javascript);
head.appendChild(element);
}

private static ScriptElement createScriptElement() {
ScriptElement script = Document.get().createScriptElement();

script.setAttribute("type", "text/javascript");
script.setAttribute("language", "javascript");

return script;
}

private static HeadElement getHead() {
if (head == null) {
Element element = Document.get().getElementsByTagName("head").getItem(0);
assert element != null : "HTML Head element required";
HeadElement head = HeadElement.as(element);
JavascriptInjector.head = head;
}
return JavascriptInjector.head;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.fathomdb.gwt.utils.resources;

import com.fathomdb.gwt.utils.rg.JavascriptResourceGenerator;
import com.google.gwt.resources.client.ResourcePrototype;
import com.google.gwt.resources.ext.ResourceGeneratorType;

@ResourceGeneratorType(JavascriptResourceGenerator.class)
public interface JavascriptResource extends ResourcePrototype {
/**
* Calls {@link com.google.gwt.dom.client.StyleInjector#injectStylesheet(String)} to inject the contents of the
* CssResource into the DOM. Repeated calls to this method on an instance of a CssResources will have no effect.
*
* @return <code>true</code> if this method mutated the DOM.
*/
boolean ensureInjected();

/**
* Provides the contents of the JavascriptResource.
*/
String getText();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.fathomdb.gwt.utils.rg;

import java.io.IOException;
import java.net.URL;

import org.apache.tools.ant.filters.StringInputStream;

import com.fathomdb.gwt.utils.resources.JavascriptInjector;
import com.fathomdb.gwt.utils.resources.JavascriptResource;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.dev.util.Util;
import com.google.gwt.resources.ext.AbstractResourceGenerator;
import com.google.gwt.resources.ext.ResourceContext;
import com.google.gwt.resources.ext.ResourceGeneratorUtil;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.StringSourceWriter;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSSourceFile;

public class JavascriptResourceGenerator extends AbstractResourceGenerator {
/**
* Java compiler has a limit of 2^16 bytes for encoding string constants in a class file. Since the max size of a
* character is 4 bytes, we'll limit the number of characters to (2^14 - 1) to fit within one record.
*/
private static final int MAX_STRING_CHUNK = 16383;

@Override
public String createAssignment(TreeLogger logger, ResourceContext context, JMethod method)
throws UnableToCompleteException {
URL[] resources = ResourceGeneratorUtil.findResources(logger, context, method);

if (resources.length != 1) {
logger.log(TreeLogger.ERROR, "Exactly one resource must be specified", null);
throw new UnableToCompleteException();
}

URL resource = resources[0];

SourceWriter sw = new StringSourceWriter();
// Write the expression to create the subtype.
sw.println("new " + JavascriptResource.class.getName() + "() {");
sw.indent();

// Convenience when examining the generated code.
sw.println("// " + resource.toExternalForm());

// Methods defined by JavascriptResource interface
writeEnsureInjected(sw);
writeGetText(sw, resource);
writeGetName(sw, method);

sw.outdent();
sw.println("}");

return sw.toString();
}

private void writeEnsureInjected(SourceWriter sw) {
sw.println("private boolean injected;");
sw.println("public boolean ensureInjected() {");
sw.indent();
sw.println("if (!injected) {");
sw.indentln("injected = true;");
sw.indentln(JavascriptInjector.class.getName() + ".inject(getText());");
sw.indentln("return true;");
sw.println("}");
sw.println("return false;");
sw.outdent();
sw.println("}");
}

private void writeGetText(SourceWriter sw, URL resource) throws UnableToCompleteException {
sw.println("public String getText() {");
sw.indent();

String toWrite = Util.readURLAsString(resource);

toWrite = maybeMinify(resource.toExternalForm(), toWrite);

if (toWrite.length() > MAX_STRING_CHUNK) {
writeLongString(sw, toWrite);
} else {
sw.println("return \"" + Generator.escape(toWrite) + "\";");
}
sw.outdent();
sw.println("}");
}

private String maybeMinify(String fileName, String js) {
// TODO: Error handling
// TODO: Don't bother if it's small anyway??
// TODO: Use heuristics to check if already minified (look for comments or whitespace?)
// TODO: Skip in hosted mode?
// TODO: Make external?
// TODO: When external, bundle? (Like images?)
Compiler compiler = new Compiler();
JSSourceFile input;
try {
input = JSSourceFile.fromInputStream(fileName, new StringInputStream(js));
} catch (IOException e) {
throw new IllegalStateException();
}
JSSourceFile[] externs = new JSSourceFile[0];
JSSourceFile[] inputs = { input };
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
/* Result result = */compiler.compile(externs, inputs, options);

return compiler.toSource();
}

private void writeGetName(SourceWriter sw, JMethod method) throws UnableToCompleteException {
sw.println("public String getName() {");
sw.indent();
sw.println("return \"" + method.getName() + "\";");
sw.outdent();
sw.println("}");
}

/**
* A single constant that is too long will crash the compiler with an out of memory error. Break up the constant and
* generate code that appends using a buffer.
*/
private void writeLongString(SourceWriter sw, String toWrite) {
sw.println("StringBuilder builder = new StringBuilder();");
int offset = 0;
int length = toWrite.length();
while (offset < length - 1) {
int subLength = Math.min(MAX_STRING_CHUNK, length - offset);
sw.print("builder.append(\"");
sw.print(Generator.escape(toWrite.substring(offset, offset + subLength)));
sw.println("\");");
offset += subLength;
}
sw.println("return builder.toString();");
}
}

0 comments on commit 5b504b9

Please sign in to comment.