Created
June 21, 2013 19:12
-
-
Save carchrae/5833575 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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