blob: 9e1fa9f11fdb5e11fd0bb8f421460f1f5e88ac1d [file] [log] [blame]
/*
* Copyright 2009 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.resources.css;
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.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ClassName;
import com.google.gwt.resources.gss.ClassNamesCollector;
import com.google.gwt.resources.rg.GssResourceGenerator;
import com.google.gwt.resources.rg.GssResourceGenerator.LoggerErrorManager;
import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
import com.google.gwt.thirdparty.common.css.compiler.ast.GssParser;
import com.google.gwt.thirdparty.common.css.compiler.ast.GssParserException;
import com.google.gwt.thirdparty.common.css.compiler.passes.CollectConstantDefinitions;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateDefinitionNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateForLoopNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.UnrollLoops;
import com.google.gwt.thirdparty.guava.common.base.CaseFormat;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.StringSourceWriter;
import com.google.gwt.util.tools.ArgHandlerFile;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
import com.google.gwt.util.tools.ToolBase;
import java.io.File;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
/**
* A utility class for creating a Java interface declaration for a given CSS
* file.
*/
public class InterfaceGenerator extends ToolBase {
private static final Comparator<String> NAME_COMPARATOR = new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
};
private static final TreeLogger.Type LOG_LEVEL = TreeLogger.WARN;
public static void main(String[] args) {
(new InterfaceGenerator()).execImpl(args);
}
private String interfaceName;
private File inputFile;
private TreeLogger logger;
private boolean standaloneFile;
private class ArgHandlerAddPackageHeader extends ArgHandlerFlag {
public ArgHandlerAddPackageHeader() {
addTagValue("-standalone", true);
}
@Override
public String getPurposeSnippet() {
return "Add package and import statements to generated interface so that "
+ "they are still functional when they stand alone.";
}
@Override
public String getLabel() {
return "addPackageHeader";
}
@Override
public boolean setFlag(boolean value) {
standaloneFile = value;
logger.log(TreeLogger.DEBUG, value ? "Not creating" : "Creating" + " a standalone file");
return true;
}
@Override
public boolean getDefaultValue() {
return standaloneFile;
}
}
private InterfaceGenerator() {
registerHandler(new ArgHandlerAddPackageHeader());
registerHandler(new ArgHandlerString() {
@Override
public String getPurpose() {
return "The name of the generated CssResource subtype";
}
@Override
public String getTag() {
return "-typeName";
}
@Override
public String[] getTagArgs() {
return new String[] {"some.package.MyCssResource"};
}
@Override
public boolean isRequired() {
return true;
}
@Override
public boolean setString(String str) {
if (str.length() == 0) {
return false;
}
if (!Character.isJavaIdentifierStart(str.charAt(0))) {
return false;
}
for (int i = 1, j = str.length(); i < j; i++) {
char c = str.charAt(i);
if (!(Character.isJavaIdentifierPart(c) || c == '.')) {
return false;
}
}
interfaceName = str;
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG, "interfaceName = " + interfaceName);
}
return true;
}
});
// -css in.css
registerHandler(new ArgHandlerFile() {
@Override
public String getPurpose() {
return "The input CSS file to process";
}
@Override
public String getTag() {
return "-css";
}
@Override
public boolean isRequired() {
return true;
}
@Override
public void setFile(File file) {
inputFile = file;
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG, "inputFile = " + file.getAbsolutePath());
}
}
});
}
@Override
protected String getDescription() {
return "Create a CssResource interface based on a CSS file";
}
private void execImpl(String[] args) {
// Set up logging
PrintWriter logWriter = new PrintWriter(System.err);
logger = new PrintWriterTreeLogger(logWriter);
((PrintWriterTreeLogger) logger).setMaxDetail(LOG_LEVEL);
// Process args or die
if (!processArgs(args)) {
System.exit(-1);
}
boolean error = false;
try {
System.out.println(process());
} catch (MalformedURLException e) {
logger.log(TreeLogger.ERROR, "Unable to load CSS", e);
error = true;
} catch (UnableToCompleteException e) {
logger.log(TreeLogger.ERROR, "Unable to process CSS", e);
error = true;
} finally {
// Make sure the logs are emitted
logWriter.flush();
}
System.exit(error ? -1 : 0);
}
/**
* Munge a CSS class name into a Java identifier.
*/
private String methodName(String className) {
StringBuilder sb = new StringBuilder();
char c = className.charAt(0);
boolean nextUpCase = false;
if (Character.isJavaIdentifierStart(c)) {
sb.append(Character.toLowerCase(c));
}
for (int i = 1, j = className.length(); i < j; i++) {
c = className.charAt(i);
if (!Character.isJavaIdentifierPart(c)) {
nextUpCase = true;
continue;
}
if (nextUpCase) {
nextUpCase = false;
c = Character.toUpperCase(c);
}
sb.append(c);
}
return sb.toString();
}
private String process() throws MalformedURLException,
UnableToCompleteException {
// Create AST
CssTree cssTree = createAst(inputFile.toURI().toURL(), logger);
// Sort all names
Set<String> names = new TreeSet<String>(NAME_COMPARATOR);
names.addAll(new ClassNamesCollector().getClassNames(cssTree));
CollectConstantDefinitions collectConstantDefinitionsPass = new CollectConstantDefinitions(
cssTree);
collectConstantDefinitionsPass.runPass();
Collection<String> renamedDefs = renameDefs(collectConstantDefinitionsPass
.getConstantDefinitions().getConstantsNames());
names.addAll(renamedDefs);
// Deduplicate method names
Set<String> methodNames = new HashSet<String>();
// Build the interface
SourceWriter sw = new StringSourceWriter();
int lastDot = interfaceName.lastIndexOf('.');
if (standaloneFile) {
sw.println("// DO NOT EDIT");
sw.println("// Automatically generated by "
+ InterfaceGenerator.class.getName());
sw.println("package " + interfaceName.substring(0, lastDot) + ";");
sw.println("import " + CssResource.class.getCanonicalName() + ";");
sw.println("import " + ClassName.class.getCanonicalName() + ";");
}
sw.println("interface " + interfaceName.substring(lastDot + 1)
+ " extends CssResource {");
sw.indent();
for (String className : names) {
String methodName = methodName(className);
while (!methodNames.add(methodName)) {
// Unusual, handles foo-bar and foo--bar
methodName += "_";
}
sw.println();
if (!methodName.equals(className)) {
sw.println("@ClassName(\"" + Generator.escape(className) + "\")");
}
sw.println("String " + methodName + "();");
}
sw.outdent();
sw.println("}");
return sw.toString();
}
/**
* In GSS, constant names are defined in upper case but a method name to access a constant in
* a CssResource interface can be written in lower camel case.
* <p>
* This method converts all constant names in a lower camel case identifier.
*/
private Collection<String> renameDefs(Iterable<String> constantsNames) {
return Lists.newArrayList(Iterables.transform(constantsNames, new Function<String, String>() {
@Override
public String apply(String constantName) {
String lowerCase = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, constantName);
// If we cannot revert the method name to the original constant name, use the
// original constant name.
// This case happens when number are used after an underscore:
// CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "DEF_1") returns def1
// but CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, "def1") returns DEF1 and the
// GssResourceGenerator is not able to match the name of the method with the name of the
// constant .
if (!constantName.equals(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE,
lowerCase))) {
return constantName;
}
return lowerCase;
}
}));
}
private CssTree createAst(URL sourceFileUrl, TreeLogger logger)
throws UnableToCompleteException {
LoggerErrorManager errorManager = new LoggerErrorManager(logger);
CssTree cssTree;
try {
cssTree = new GssParser(GssResourceGenerator.readUrlContent(sourceFileUrl, logger)).parse();
} catch (GssParserException e) {
logger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
throw new UnableToCompleteException();
}
new CreateDefinitionNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
// Can create new style classes
new CreateForLoopNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
new UnrollLoops(cssTree.getMutatingVisitController(), errorManager).runPass();
return cssTree;
}
}