/*
* Copyright 2008 Google Inc.
*
* 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.libideas.resources.tools;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.resource.ResourceOracle;
import com.google.gwt.dev.resource.impl.PathPrefix;
import com.google.gwt.dev.resource.impl.PathPrefixSet;
import com.google.gwt.dev.resource.impl.ResourceFilter;
import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.libideas.resources.client.CssResource;
import com.google.gwt.libideas.resources.client.DataResource;
import com.google.gwt.libideas.resources.client.ExternalTextResource;
import com.google.gwt.libideas.resources.client.ImageResource;
import com.google.gwt.libideas.resources.client.ImmutableResourceBundle;
import com.google.gwt.libideas.resources.client.ResourcePrototype;
import com.google.gwt.libideas.resources.client.TextResource;
import com.google.gwt.util.tools.ArgHandlerDir;
import com.google.gwt.util.tools.ArgHandlerExtra;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
import com.google.gwt.util.tools.ToolBase;
/**
* Given a Java package, create an {@link ImmutableResourceBundle} interface.
*/
public class MakeBundle extends ToolBase {
private static final List<String> JAVA_KEYWORDS = Arrays.asList("abstract",
"continue", "for", " new", "switch", "assert", "default", "goto",
"package", "synchronized", "boolean", "do", "if", " private", "this",
"break", "double", "implements", "protected", "throw", "byte", "else",
"import", "public", "throws", "case", "enum", "instanceof", "return",
"transient", "catch", "extends", "int", "short", "try", "char", "final",
"interface", "static", "void", "class", "finally", "long", "strictfp",
"volatile", "const", "float", "native", "super", "while");
private static final String[] IMAGE_TYPES = {"gif", "jpg", "png"};
private static final String[] SOUND_TYPES = {"mp3", "wav"};
private static final String[] TEXT_TYPES = {
"htm", "html", "java", "json", "txt", "xhtml", "xml"};
private static final Map<String, Class<? extends ResourcePrototype>> typeMap = new TreeMap<String, Class<? extends ResourcePrototype>>();
static {
typeMap.put("css", CssResource.class);
for (String type : IMAGE_TYPES) {
typeMap.put(type, ImageResource.class);
}
// This loop runs twice
boolean external = false;
do {
external = !external;
for (String type : TEXT_TYPES) {
typeMap.put((external ? "e" : "") + type, external
? ExternalTextResource.class : TextResource.class);
}
} while (external);
}
/**
* Entry point.
*/
public static void main(String[] args) {
PrintWriter pw = new PrintWriter(System.out);
PrintWriterTreeLogger logger = new PrintWriterTreeLogger(pw);
logger.setMaxDetail(TreeLogger.INFO);
try {
(new MakeBundle()).exec(logger, args);
} finally {
pw.close();
}
}
private String bundleName;
private final ArgHandlerString bundleNameHandler = new ArgHandlerString() {
@Override
public String getPurpose() {
return "The bundle's class simple source name";
}
@Override
public String getTag() {
return "-name";
}
@Override
public String[] getTagArgs() {
return new String[] {"FooBundle"};
}
@Override
public boolean isRequired() {
return true;
}
@Override
public boolean setString(String str) {
bundleName = str;
return true;
}
};
private File outDir;
private final ArgHandlerDir outDirHandler = new ArgHandlerDir() {
@Override
public String getPurpose() {
return "The output directory";
}
@Override
public String getTag() {
return "-out";
}
@Override
public void setDir(File dir) {
outDir = dir;
}
};
private boolean showTypes;
private final ArgHandlerFlag showTypesHandler = new ArgHandlerFlag() {
@Override
public String getPurpose() {
return "Show the file extension to resource type map";
}
@Override
public String getTag() {
return "-showTypes";
}
@Override
public boolean setFlag() {
return showTypes = true;
}
};
private String packageName;
private final ArgHandlerString packageNameHandler = new ArgHandlerString() {
@Override
public String getPurpose() {
return "The binary name of the package to look for resources in";
}
@Override
public String getTag() {
return "-package";
}
@Override
public String[] getTagArgs() {
return new String[] {"com.foo.client"};
}
@Override
public boolean isRequired() {
return true;
}
@Override
public boolean setString(String str) {
packageName = str;
return true;
}
};
private final List<URL> files = new ArrayList<URL>();
private final ArgHandlerExtra filesHandler = new ArgHandlerExtra() {
@Override
public boolean addExtraArg(String arg) {
try {
files.add((new File(arg)).toURL());
} catch (IOException e) {
return false;
}
return true;
}
@Override
public String getPurpose() {
return "Restrict the files included in the Bundle";
}
@Override
public String[] getTagArgs() {
return new String[] {"file1.txt", "subpackage/file2.png"};
}
};
/**
* Utility class.
*/
private MakeBundle() {
}
@Override
protected String getDescription() {
return "Generate an ImmutableResourceBundle definition. Run this class "
+ "with the same classpath used to invoke the GWT compiler on your"
+ "module.";
}
/**
* Instantiate the ResourceOracle used to find resources via the context class
* loader.
*/
private ResourceOracle createResourceOracle(TreeLogger logger) {
logger = logger.branch(TreeLogger.DEBUG, "Creating ResourceOracle");
ResourceOracleImpl oracle = new ResourceOracleImpl(logger);
PathPrefixSet pps = new PathPrefixSet();
pps.add(new PathPrefix(packageName.replace('.', '/') + '/',
// Eliminate stuff that's definitely not source material
new ResourceFilter() {
public boolean allows(String path) {
return !(path.endsWith(".class") || path.contains("/."));
}
}));
oracle.setPathPrefixes(pps);
ResourceOracleImpl.refresh(logger, oracle);
return oracle;
}
/**
* Open a PrintWriter to write into the output file.
*/
private PrintWriter createWriter(TreeLogger logger) {
File output = new File(outDir, packageName.replace(".", File.separator)
+ File.separator + bundleName);
logger = logger.branch(TreeLogger.INFO, "Writing to " + output.getPath());
output.getParentFile().mkdirs();
PrintWriter out;
try {
out = new PrintWriter(new FileWriter(output));
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to create output file", e);
return null;
}
return out;
}
private void exec(TreeLogger logger, String[] args) {
registerHandler(showTypesHandler);
registerHandler(packageNameHandler);
registerHandler(bundleNameHandler);
registerHandler(outDirHandler);
registerHandler(filesHandler);
if (!processArgs(args)) {
return;
}
if (showTypes) {
for (Map.Entry<String, Class<? extends ResourcePrototype>> entry : typeMap.entrySet()) {
logger.log(TreeLogger.INFO, entry.getKey() + " : "
+ entry.getValue().getSimpleName());
}
}
if (outDir == null) {
try {
outDir = new File(".").getCanonicalFile();
} catch (IOException e) {
logger.log(TreeLogger.ERROR,
"Unable to determine current working directory", e);
return;
}
}
ResourceOracle oracle = createResourceOracle(logger);
PrintWriter out = createWriter(logger);
// Sort the entries to provide consistent behavior
TreeSet<Resource> sortedResources = new TreeSet<Resource>(
new Comparator<Resource>() {
public int compare(Resource o1, Resource o2) {
return o1.getPath().compareTo(o2.getPath());
}
});
sortedResources.addAll(oracle.getResources());
writeMethods(logger, out, sortedResources);
out.close();
}
/**
* Determine the type of {@link ResourcePrototype} that will be used to
* represent the resource.
*/
private Class<? extends ResourcePrototype> getResourceType(String path) {
int start = path.lastIndexOf('/');
path = path.substring(start + 1);
int end = path.lastIndexOf('.');
if (end != -1) {
path = path.substring(end + 1).toLowerCase();
if (typeMap.containsKey(path)) {
return typeMap.get(path);
}
}
return DataResource.class;
}
/**
* Create a Java identifier for a resource.
*/
private String makeShortName(String path) {
int start = path.lastIndexOf('/');
path = path.substring(start + 1);
int end = path.lastIndexOf('.');
if (end != -1) {
path = path.substring(0, end);
}
if (JAVA_KEYWORDS.contains(path)) {
path += "_";
}
assert Util.isValidJavaIdent(path);
return path;
}
/**
* Write the contents of the Bundle.
*/
private void writeMethods(TreeLogger logger, PrintWriter out,
Set<Resource> resources) {
logger = logger.branch(TreeLogger.DEBUG, "Writing contents");
out.println("// AUTOMATICALLY GENERATED CLASS -- DO NOT EDIT");
out.println("package " + packageName + ";");
out.println("/*" + "* Compose this interface or use with GWT.create(). */");
out.println("public interface " + makeShortName(bundleName) + " extends "
+ ImmutableResourceBundle.class.getCanonicalName() + " {");
for (Resource s : resources) {
// Check the resource against the restricted set of files
if (!(files.isEmpty() || files.contains(s.getURL()))) {
logger.log(TreeLogger.DEBUG, "Ignoring " + s.getLocation());
continue;
}
logger.log(TreeLogger.DEBUG, s.getLocation() + " " + s.getPath());
String shortName = makeShortName(s.getPath());
out.println(" @Resource(\"" + s.getPath() + "\")");
Class<? extends ResourcePrototype> clazz = getResourceType(s.getPath());
out.println(" " + clazz.getCanonicalName() + " " + shortName + "();\n");
}
out.println("}");
}
}