| /* |
| * 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; |
| } |
| } |