Skip to content

Instantly share code, notes, and snippets.

@carchrae
Created June 21, 2013 19:12
Show Gist options
  • Save carchrae/5833575 to your computer and use it in GitHub Desktop.
Save carchrae/5833575 to your computer and use it in GitHub Desktop.
package glwt.dust.rebind;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import com.google.gwt.core.ext.CachedGeneratorResult;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.resource.ResourceOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
public class DustBundleGenerator extends IncrementalGenerator {
private HashMap<String, Serializable> clientDataMap;
private ArrayList<String> paths;
private ArrayList<String> files;
private String packageName;
JClassType userType;
private HashMap<String, Long> pathToModifiedMap;
@Override
public RebindResult generateIncrementally(TreeLogger logger,
GeneratorContext context, String typeName)
throws UnableToCompleteException {
String clientDataKey = typeName + "_clientDataKey";
CachedGeneratorResult cachedGeneratorResult = context
.getCachedGeneratorResult();
if (cachedGeneratorResult != null)
clientDataMap = (HashMap<String, Serializable>) cachedGeneratorResult
.getClientData(clientDataKey);
if (clientDataMap == null) {
clientDataMap = new HashMap<String, Serializable>();
}
initTypeAndPackageNames(logger, context, typeName);
boolean filesChanged = scanFiles(context);
if (filesChanged)
incrementVersion();
String lastName = (String) clientDataMap.get("lastName");
if (!filesChanged && lastName != null)
return new RebindResult(RebindMode.USE_ALL_CACHED, lastName);
String name = doGenerate(logger, context, typeName);
clientDataMap.put("lastName", name);
RebindResult result = new RebindResult(RebindMode.USE_ALL_NEW, name);
result.putClientData(clientDataKey, clientDataMap);
return result;
}
private void incrementVersion() {
clientDataMap.put("version", System.currentTimeMillis());
}
private String getVersion() {
Serializable version = clientDataMap.get("version");
if (version == null) {
incrementVersion();
version = clientDataMap.get("version");
}
return String.valueOf(version);
}
public void initTypeAndPackageNames(TreeLogger logger,
GeneratorContext context, String typeName)
throws UnableToCompleteException {
TypeOracle typeOracle = context.getTypeOracle();
try {
userType = typeOracle.getType(typeName);
} catch (NotFoundException e) {
logger.log(TreeLogger.ERROR, "Unable to find metadata for type: "
+ typeName, e);
throw new UnableToCompleteException();
}
if (userType.isInterface() == null) {
logger.log(TreeLogger.ERROR, userType.getQualifiedSourceName()
+ " is not an interface", null);
throw new UnableToCompleteException();
}
packageName = userType.getPackage().getName();
}
private boolean scanFiles(GeneratorContext context) {
ResourceOracle resourcesOracle = context.getResourcesOracle();
String packagePath = packageName.replace(".", "/");
String fileSuffix = ".dust.html";
int suffixLength = fileSuffix.length();
pathToModifiedMap = (HashMap<String, Long>) clientDataMap
.get("fileModifyMap");
if (pathToModifiedMap == null)
clientDataMap.put("fileModifyMap",
pathToModifiedMap = new HashMap<String, Long>());
paths = new ArrayList<String>();
files = new ArrayList<String>();
boolean changed = false;
Set<String> allPaths = new HashSet<String>(pathToModifiedMap.keySet());
for (Resource r : resourcesOracle.getResources()) {
String path = r.getPath();
if (path.startsWith(packagePath) && path.endsWith(fileSuffix)) {
String file = path.replaceFirst(packagePath, "");
file = file.substring(1, file.length() - suffixLength);
paths.add(path);
files.add(file);
Long oldTime = pathToModifiedMap.put(path, r.getLastModified());
if (oldTime == null || oldTime < r.getLastModified())
changed = true;
allPaths.remove(path);
}
}
if (allPaths.size() > 0) {
// file has been deleted
changed = true;
}
return changed;
}
@Override
public long getVersionId() {
return 1L;
}
/**
* 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;
public String doGenerate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
String className = getClassName();
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
packageName, className);
composerFactory.addImplementedInterface(userType
.getQualifiedSourceName());
composerFactory
.addImport("com.google.gwt.resources.client.ClientBundle");
composerFactory
.addImport("com.google.gwt.resources.client.TextResource");
composerFactory.addImport("java.util.ArrayList");
composerFactory.addImport("java.util.HashMap");
composerFactory.addImport("com.google.gwt.core.client.GWT");
PrintWriter pw = context.tryCreate(logger, packageName, className);
if (pw != null) {
SourceWriter sw = composerFactory.createSourceWriter(context, pw);
// Write the expression to create the subtype.
sw.indent();
sw.indent();
sw.println("private ArrayList<String> templates = new ArrayList<String>();");
sw.println("private HashMap<String,String> sourceMap = new HashMap<String,String>();");
sw.println("private HashMap<String,String> compiledMap = new HashMap<String,String>();");
sw.println("private HashMap<String,Long> modifiedMap = new HashMap<String,Long>();");
sw.println("private boolean hasError = false;");
sw.println("");
sw.println("public interface Resources extends ClientBundle {");
sw.indent();
for (int i = 0; i < paths.size(); i++) {
String path = paths.get(i);
String file = files.get(i);
sw.println("//template for " + file);
sw.println("@Source(\"" + path + "\")");
String name = getMethodName(i, path, file);
sw.println("TextResource " + name + "();");
}
sw.println("}");
sw.outdent();
sw.println("private Resources resources = GWT.create(Resources.class); ");
sw.println("");
// create constructor
sw.println(className + "(){");
sw.indent();
for (int i = 0; i < paths.size(); i++) {
String path = paths.get(i);
String file = files.get(i);
String method = getMethodName(i, path, file);
generateDustLoader2(sw, file, method, path);
}
sw.println("}");
sw.outdent();
sw.println("");
sw.println("public void showError(String file,String path,String source, String message) {");
sw.indent();
sw.println(" glwt.dust.client.ErrorWindow.show(file,path,source,message);");
sw.outdent();
sw.println("}");
sw.println("");
sw.println("public java.util.List<String> getTemplates() {");
sw.indent();
sw.println("return templates;");
sw.outdent();
sw.println("}");
sw.println("public String getSource(String file) {");
sw.indent();
sw.println("return sourceMap.get(file);");
sw.outdent();
sw.println("}");
sw.println("public String getCompiled(String file) {");
sw.indent();
sw.println("return compiledMap.get(file);");
sw.outdent();
sw.println("}");
sw.println("public Long getModified(String file) {");
sw.indent();
sw.println("return modifiedMap.get(file);");
sw.outdent();
sw.println("}");
sw.println("");
sw.println("public boolean hasError() {");
sw.indent();
sw.println("return hasError;");
sw.outdent();
sw.println("}");
sw.outdent();
sw.commit(logger);
}
return composerFactory.getCreatedClassName();
}
public String getClassName() {
String className = userType.getName();
className = className.replace('.', '_') + "Impl" + getVersion();
return className;
}
public String getMethodName(int i, String path, String file) {
return "file" + i;
}
// public void generateDustLoader(SourceWriter sw, String file, String
// source,
// String path) {
// sw.println("try {");
// sw.indent();
// sw.println("String file = \"" + file + "\";");
// sw.println("String source;");
// writeString(sw, "source", source);
// sw.println("String compiled = glwt.dust.client.Dust.compile(file, source);");
// sw.println("glwt.dust.client.Dust.loadSource(compiled);");
// sw.println("templates.add(file);");
// sw.outdent();
// sw.println("}");
// sw.println("catch (Exception e){ e.printStackTrace(); com.google.gwt.user.client.Window.alert(\"Failed to compile : "
// + path + " :: \" + e.getMessage());}");
// }
public void generateDustLoader2(SourceWriter sw, String file,
String sourceMethod, String path) {
sw.indent();
sw.println("{");
sw.println("String file = \"" + file + "\";");
sw.println("String source = resources." + sourceMethod
+ "().getText();");
sw.println("try {");
sw.indent();
sw.println("String compiled = glwt.dust.client.Dust.compile(file, source);");
sw.println("glwt.dust.client.Dust.loadSource(compiled);");
sw.println("templates.add(file);");
sw.println("String old = sourceMap.put(file,source);");
sw.println("assert(old==null):\"Template name '" + file
+ "' has been stored twice\";");
sw.println("compiledMap.put(file,compiled);");
Long modified = pathToModifiedMap.get(path);
if (modified != null) {
sw.println("modifiedMap.put(file," + modified + "L);");
}
sw.outdent();
sw.println("}");
sw.println("catch (com.google.gwt.core.client.JavaScriptException e){ e.printStackTrace(); showError(file,\""
+ path + "\",source,e.getMessage()); hasError=true; }");
sw.outdent();
sw.println("}");
}
private void writeString(SourceWriter sw, String var, String toWrite) {
if (toWrite.length() > MAX_STRING_CHUNK) {
writeLongStringToVar(sw, var, toWrite);
} else {
sw.println(var + " = \"" + Generator.escape(toWrite) + "\";");
}
}
/**
* 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 writeLongStringToVar(SourceWriter sw, String var,
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(var + " = builder.toString();");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment