All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.resources.rg.GssResourceGenerator Maven / Gradle / Ivy

There is a newer version: 2.8.2-v20191108
Show newest version
/*
 * Copyright 2014 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.rg;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.SelectionProperty;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.util.Util;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.resources.client.ClientBundle.Source;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ClassName;
import com.google.gwt.resources.client.CssResource.Import;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.resources.client.CssResource.NotStrict;
import com.google.gwt.resources.client.CssResource.Shared;
import com.google.gwt.resources.client.ResourcePrototype;
import com.google.gwt.resources.converter.Css2Gss;
import com.google.gwt.resources.converter.Css2GssConversionException;
import com.google.gwt.resources.ext.ClientBundleRequirements;
import com.google.gwt.resources.ext.ResourceContext;
import com.google.gwt.resources.ext.ResourceGeneratorUtil;
import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
import com.google.gwt.resources.gss.BooleanConditionCollector;
import com.google.gwt.resources.gss.CollectAndRemoveConstantDefinitions;
import com.google.gwt.resources.gss.CreateRuntimeConditionalNodes;
import com.google.gwt.resources.gss.CssPrinter;
import com.google.gwt.resources.gss.ExtendedEliminateConditionalNodes;
import com.google.gwt.resources.gss.ExternalClassesCollector;
import com.google.gwt.resources.gss.GwtGssFunctionMapProvider;
import com.google.gwt.resources.gss.ImageSpriteCreator;
import com.google.gwt.resources.gss.PermutationsCollector;
import com.google.gwt.resources.gss.RecordingBidiFlipper;
import com.google.gwt.resources.gss.RenamingSubstitutionMap;
import com.google.gwt.resources.gss.RuntimeConditionalBlockCollector;
import com.google.gwt.resources.gss.ValidateRuntimeConditionalNode;
import com.google.gwt.resources.rg.CssResourceGenerator.JClassOrderComparator;
import com.google.gwt.thirdparty.common.css.MinimalSubstitutionMap;
import com.google.gwt.thirdparty.common.css.PrefixingSubstitutionMap;
import com.google.gwt.thirdparty.common.css.SourceCode;
import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
import com.google.gwt.thirdparty.common.css.SubstitutionMap;
import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompositeValueNode;
import com.google.gwt.thirdparty.common.css.compiler.ast.CssDefinitionNode;
import com.google.gwt.thirdparty.common.css.compiler.ast.CssNumericNode;
import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
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.AbbreviatePositionalValues;
import com.google.gwt.thirdparty.common.css.compiler.passes.CheckDependencyNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.CollectConstantDefinitions;
import com.google.gwt.thirdparty.common.css.compiler.passes.CollectMixinDefinitions;
import com.google.gwt.thirdparty.common.css.compiler.passes.ColorValueOptimizer;
import com.google.gwt.thirdparty.common.css.compiler.passes.ConstantDefinitions;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateComponentNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConstantReferences;
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.CreateMixins;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateStandardAtRuleNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.CreateVendorPrefixedKeyframes;
import com.google.gwt.thirdparty.common.css.compiler.passes.CssClassRenaming;
import com.google.gwt.thirdparty.common.css.compiler.passes.DisallowDuplicateDeclarations;
import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateEmptyRulesetNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateUnitsFromZeroNumericValues;
import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateUselessRulesetNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.HandleUnknownAtRuleNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.MarkNonFlippableNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.MarkRemovableRulesetNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.MergeAdjacentRulesetNodesWithSameDeclarations;
import com.google.gwt.thirdparty.common.css.compiler.passes.MergeAdjacentRulesetNodesWithSameSelector;
import com.google.gwt.thirdparty.common.css.compiler.passes.ProcessComponents;
import com.google.gwt.thirdparty.common.css.compiler.passes.ProcessKeyframes;
import com.google.gwt.thirdparty.common.css.compiler.passes.ProcessRefiners;
import com.google.gwt.thirdparty.common.css.compiler.passes.ReplaceConstantReferences;
import com.google.gwt.thirdparty.common.css.compiler.passes.ReplaceMixins;
import com.google.gwt.thirdparty.common.css.compiler.passes.ResolveCustomFunctionNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.SplitRulesetNodes;
import com.google.gwt.thirdparty.common.css.compiler.passes.UnrollLoops;
import com.google.gwt.thirdparty.common.css.compiler.passes.ValidatePropertyValues;
import com.google.gwt.thirdparty.guava.common.base.CaseFormat;
import com.google.gwt.thirdparty.guava.common.base.Charsets;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.base.Strings;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet.Builder;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.io.ByteSource;
import com.google.gwt.thirdparty.guava.common.io.Resources;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.StringSourceWriter;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.Adler32;

/**
 * This generator parses and compiles a GSS file to a css string and generates the implementation
 * of the corresponding CssResource interface.
 */
public class GssResourceGenerator extends AbstractCssResourceGenerator implements
    SupportsGeneratorResultCaching {

  /**
   * GssOptions contains the values of all configuration properties that can be used with
   * GssResource.
   */
  public static class GssOptions {
    private final boolean enabled;
    private final AutoConversionMode autoConversionMode;
    private final boolean gssDefaultInUiBinder;

    public GssOptions(boolean enabled, AutoConversionMode autoConversionMode, boolean gssDefaultInUiBinder) {
      this.enabled = enabled;
      this.autoConversionMode = autoConversionMode;
      this.gssDefaultInUiBinder = gssDefaultInUiBinder;
    }

    public boolean isEnabled() {
      return enabled;
    }

    public boolean isGssDefaultInUiBinder() {
      return gssDefaultInUiBinder;
    }

    public boolean isAutoConversionOff() {
      return autoConversionMode == AutoConversionMode.OFF;
    }

    public boolean isLenientConversion() {
      return autoConversionMode == AutoConversionMode.LENIENT;
    }
  }

  /**
   * Different conversion modes from css to gss.
   */
  public enum AutoConversionMode { STRICT, LENIENT, OFF }

  /*
   * TODO(dankurka): This is a nasty hack to get the compiler to output all @def's
   * it has seen in a compile. Once GSS migration is done this needs to be removed.
   */
  private static boolean shouldEmitVariables;
  private static PrintWriter printWriter;
  private static Set writtenAtDefs = new HashSet<>();

  private static final String KEY_ENABLE_GSS = "CssResource.enableGss";
  private static final String KEY_GSS_DEFAULT_IN_UIBINDER = "CssResource.gssDefaultInUiBinder";

  static {
    String varFileName = System.getProperty("emitGssVarNameFile");
    shouldEmitVariables = varFileName != null;
    if (shouldEmitVariables) {
      try {
        File file = new File(varFileName);
        file.createNewFile();
        printWriter = new PrintWriter(new FileOutputStream(file));
      } catch (Exception e) {
        System.err.println("Error while opening file");
        e.printStackTrace();
        System.exit(-1);
      }
    }
  }

  public static SourceCode readUrlContent(URL fileUrl, TreeLogger logger) throws UnableToCompleteException {
    TreeLogger branchLogger = logger.branch(TreeLogger.DEBUG,
            "Reading GSS stylesheet " + fileUrl.toExternalForm());
    try {
      ByteSource byteSource = Resources.asByteSource(fileUrl);
      // default charset
      Charset charset = Charsets.UTF_8;

      // check if the stylesheet doesn't include a @charset at-rule
      String styleSheetCharset = extractCharset(byteSource);
      if (styleSheetCharset != null) {
        try {
          charset = Charset.forName(styleSheetCharset);
        } catch (UnsupportedCharsetException e) {
          logger.log(Type.ERROR, "Unsupported charset found: " + styleSheetCharset);
          throw new UnableToCompleteException();
        }
      }

      String fileContent = byteSource.asCharSource(charset).read();
      // If the stylesheet specified a charset, we have to remove the at-rule otherwise the GSS
      // compiler will fail.
      if (styleSheetCharset != null) {
        int charsetAtRuleLength = CHARSET_MIN_LENGTH + styleSheetCharset.length();
        // replace charset at-rule by blanks to keep correct source location of the rest of
        // the stylesheet.
        fileContent = Strings.repeat(" ", charsetAtRuleLength) +
                fileContent.substring(charsetAtRuleLength);
      }
      return new SourceCode(fileUrl.getFile(), fileContent);

    } catch (IOException e) {
      branchLogger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
    }
    throw new UnableToCompleteException();
  }

  public static GssOptions getGssOptions(PropertyOracle propertyOracle, TreeLogger logger) throws UnableToCompleteException {
    boolean gssEnabled;
    boolean gssDefaultInUiBinder;
    AutoConversionMode conversionMode;

    try {
      ConfigurationProperty enableGssProp =
          propertyOracle.getConfigurationProperty(KEY_ENABLE_GSS);
      String enableGss = enableGssProp.getValues().get(0);
      gssEnabled = Boolean.parseBoolean(enableGss);
    } catch (BadPropertyValueException ex) {
      logger.log(Type.ERROR, "Unable to determine if GSS need to be used");
      throw new UnableToCompleteException();
    }
    try {
      conversionMode = Enum.valueOf(AutoConversionMode.class, propertyOracle
          .getConfigurationProperty(KEY_CONVERSION_MODE).getValues().get(0)
          .toUpperCase(Locale.ROOT));
    } catch (BadPropertyValueException ex) {
      logger.log(Type.ERROR, "Unable to conversion mode for GSS");
      throw new UnableToCompleteException();
    }
    try {
      ConfigurationProperty uiBinderGssDefaultProp =
          propertyOracle.getConfigurationProperty(KEY_GSS_DEFAULT_IN_UIBINDER);
      String uiBinderGssDefaultValue = uiBinderGssDefaultProp.getValues().get(0);
      gssDefaultInUiBinder =  Boolean.parseBoolean(uiBinderGssDefaultValue);
    } catch (BadPropertyValueException ex) {
      logger.log(Type.ERROR, "Unable to determine default for GSS in UiBinder");
      throw new UnableToCompleteException();
    }
    return new GssOptions(gssEnabled, conversionMode, gssDefaultInUiBinder);
  }

  private static synchronized void write(Set variables) {
    for (String atDef : variables) {
      if (writtenAtDefs.add(atDef)) {
        printWriter.println("@def " + atDef + " 1px;");
      }
    }
    printWriter.flush();
  }

  /**
   * {@link ErrorManager} used to log the errors and warning messages produced by the different
   * {@link com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass}.
   */
  public static class LoggerErrorManager implements ErrorManager {
    private final TreeLogger logger;
    private boolean hasErrors;

    public LoggerErrorManager(TreeLogger logger) {
      this.logger = logger;
    }

    @Override
    public void generateReport() {
      // do nothing
    }

    @Override
    public boolean hasErrors() {
      return hasErrors;
    }

    @Override
    public void report(GssError error) {
      String fileName = "";
      String location = "";
      SourceCodeLocation codeLocation = error.getLocation();

      if (codeLocation != null) {
        fileName = codeLocation.getSourceCode().getFileName();
        location = "[line: " + codeLocation.getBeginLineNumber() + " column: " + codeLocation
            .getBeginIndexInLine() + "]";
      }

      logger.log(Type.ERROR, "Error in " + fileName + location + ": " + error.getMessage());
      hasErrors = true;
    }

    @Override
    public void reportWarning(GssError warning) {
      logger.log(Type.WARN, warning.getMessage());
    }
  }

  private static class ConversionResult {
    final String gss;
    final Map defNameMapping;

    private ConversionResult(String gss, Map defNameMapping) {
      this.gss = gss;
      this.defNameMapping = defNameMapping;
    }
  }

  private static class RenamingResult {
    final Map mapping;
    final Set externalClassCandidate;

    private RenamingResult(Map mapping, Set externalClassCandidate) {
      this.mapping = mapping;
      this.externalClassCandidate = externalClassCandidate;
    }
  }

  private static class CssParsingResult {
    final CssTree tree;
    final List permutationAxes;
    final Map originalConstantNameMapping;
    final Set trueConditions;

    private CssParsingResult(CssTree tree, List permutationAxis, Set trueConditions,
        Map originalConstantNameMapping) {
      this.tree = tree;
      this.permutationAxes = permutationAxis;
      this.originalConstantNameMapping = originalConstantNameMapping;
      this.trueConditions = trueConditions;
    }
  }

  /**
   * Predicate implementation used during the conversion to GSS.
   */
  private static class ConfigurationPropertyMatcher implements Predicate {
    private final PropertyOracle propertyOracle;
    private final TreeLogger logger;

    private boolean error;

    ConfigurationPropertyMatcher(ResourceContext context, TreeLogger logger) {
      this.logger = logger;
      propertyOracle = context.getGeneratorContext().getPropertyOracle();
    }

    @Override
    public boolean apply(String booleanCondition) {
      // if the condition is negated, the string parameter contains the ! operator if this method
      // is called during the conversion to GSS
      if (booleanCondition.startsWith("!")) {
        booleanCondition = booleanCondition.substring(1);
      }

      try {
        ConfigurationProperty property = propertyOracle.getConfigurationProperty(booleanCondition);
        boolean valid = checkPropertyIsSingleValueAndBoolean(property, logger);

        error |= !valid;

        return valid;
      } catch (BadPropertyValueException e) {
        return false;
      }
    }
  }

  // To be sure to avoid conflict during the style classes renaming between different GssResources,
  // we will create a different prefix for each GssResource. We use a MinimalSubstitutionMap
  // that will create a String with 1-6 characters in length but keeping the length of the prefix
  // as short as possible. For instance if we have two GssResources to compile, the  prefix
  // for the first resource will be 'a' and the prefix for the second resource will be 'b' and so on
  private static final SubstitutionMap resourcePrefixBuilder = new MinimalSubstitutionMap();
  private static final String KEY_CONVERSION_MODE = "CssResource.conversionMode";
  private static final String KEY_STYLE = "CssResource.style";
  private static final String ALLOWED_AT_RULE = "CssResource.allowedAtRules";
  private static final String ALLOWED_FUNCTIONS = "CssResource.allowedFunctions";
  private static final String KEY_OBFUSCATION_PREFIX = "CssResource.obfuscationPrefix";
  private static final String KEY_CLASS_PREFIX = "cssResourcePrefix";
  private static final String KEY_BY_CLASS_AND_METHOD = "cssResourceClassAndMethod";
  private static final String KEY_HAS_CACHED_DATA = "hasCachedData";
  private static final String KEY_SHARED_METHODS = "sharedMethods";
  private static final char[] BASE32_CHARS = new char[]{
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
      'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', '0', '1',
      '2', '3', '4', '5', '6'};
  // We follow CSS specification to detect the charset:
  // - Authors using an @charset rule must place the rule at the very beginning of the style sheet,
  // preceded by no characters.
  // - @charset must be written literally, i.e., the 10 characters '@charset "' (lowercase, no
  // backslash escapes), followed by the encoding name, followed by '";'.
  // see: http://www.w3.org/TR/CSS2/syndata.html#charset
  private static final Pattern CHARSET = Pattern.compile("^@charset \"([^\"]*)\";");
  private static final int CHARSET_MIN_LENGTH = "@charset \"\";".length();

  /**
   * Returns the import prefix for a type, including the trailing hyphen.
   */
  public static String getImportPrefix(JClassType importType) {
    String prefix = importType.getSimpleSourceName();
    ImportedWithPrefix exp = importType.getAnnotation(ImportedWithPrefix.class);
    if (exp != null) {
      prefix = exp.value();
    }

    return prefix + "-";
  }

  private static String encode(long id) {
    assert id >= 0;

    StringBuilder b = new StringBuilder();

    // Use only guaranteed-alpha characters for the first character
    b.append(BASE32_CHARS[(int) (id & 0xf)]);
    id >>= 4;

    while (id != 0) {
      b.append(BASE32_CHARS[(int) (id & 0x1f)]);
      id >>= 5;
    }

    return b.toString();
  }

  private static boolean checkPropertyIsSingleValueAndBoolean(ConfigurationProperty property,
      TreeLogger logger) {
    List values = property.getValues();

    if (values.size() > 1) {
      logger.log(Type.ERROR, "The configuration property " + property.getName() + " is used in " +
          "a conditional css and cannot be a multi-valued property");
      return false;
    }

    String value = values.get(0);

    if (!"true".equals(value) && !"false".equals(value)) {
      logger.log(Type.ERROR, "The configuration property " + property.getName() + " is used in " +
          "a conditional css. Its value must be either \"true\" or \"false\"");
      return false;
    }

    return true;
  }

  /**
   * Temporary method needed when GSS and the old CSS syntax are both supported by the sdk.
   * It aims to choose the right resource file according to whether gss is enabled or not. If gss is
   * enabled, it will try to find the resource file ending by .gss first. If GSS is disabled it will
   * try to find the .css file. This logic is applied even if a
   * {@link com.google.gwt.resources.client.ClientBundle.Source} annotation is used to define
   * the resource file.
   * 

* This method can be deleted once the support for the old CssResource is removed and use directly * ResourceGeneratorUtil.findResources(). */ static URL[] findResources(TreeLogger logger, ResourceContext context, JMethod method, boolean gssEnabled) throws UnableToCompleteException { boolean isSourceAnnotationUsed = method.getAnnotation(Source.class) != null; if (!isSourceAnnotationUsed) { // ResourceGeneratorUtil will try to find automatically the resource file. Give him the right // extension to use first String[] extensions = gssEnabled ? new String[]{".gss", ".css"} : new String[]{".css", ".gss"}; return ResourceGeneratorUtil.findResources(logger, context, method, extensions); } // find the original resource files specified by the @Source annotation URL[] originalResources = ResourceGeneratorUtil.findResources(logger, context, method); URL[] resourcesToUse = new URL[originalResources.length]; String preferredExtension = gssEnabled ? ".gss" : ".css"; // Try to find all the resources by using the preferred extension according to whether gss is // enabled or not. If one file with the preferred extension is missing, return the original // resource files otherwise return the preferred files. String[] sourceFiles = method.getAnnotation(Source.class).value(); for (int i = 0; i < sourceFiles.length; i++) { String original = sourceFiles[i]; if (!original.endsWith(preferredExtension) && original.length() > 4) { String preferredFile = original.substring(0, original.length() - 4) + preferredExtension; // try to find the resource relative to the package String path = method.getEnclosingType().getPackage().getName().replace('.', '/') + '/'; URL preferredUrl = ResourceGeneratorUtil .tryFindResource(logger, context.getGeneratorContext(), context, path + preferredFile); if (preferredUrl == null) { // if it doesn't exist, assume it is absolute preferredUrl = ResourceGeneratorUtil .tryFindResource(logger, context.getGeneratorContext(), context, preferredFile); } if (preferredUrl == null) { // avoid to mix gss and css, if one file with the preferred extension is missing return originalResources; } logger.log(Type.DEBUG, "Preferred resource file found: " + preferredFile + ". This file " + "will be used in replacement of " + original); resourcesToUse[i] = preferredUrl; } else { // gss and css files shouldn't be used together for a same resource. So if one of the file // is using the the preferred extension, return the original resources. If the dev has mixed // gss and ccs files, that will fail later. return originalResources; } } return resourcesToUse; } private Map cssParsingResultMap; private Set allowedNonStandardFunctions; private LoggerErrorManager errorManager; private JMethod getTextMethod; private JMethod ensuredInjectedMethod; private JMethod getNameMethod; private String obfuscationPrefix; private CssObfuscationStyle obfuscationStyle; private Set allowedAtRules; private Map> replacementsByClassAndMethod; private Map replacementsForSharedMethods; private final GssOptions gssOptions; public GssResourceGenerator(GssOptions gssOptions) { this.gssOptions = gssOptions; } @Override public String createAssignment(TreeLogger logger, ResourceContext context, JMethod method) throws UnableToCompleteException { CssParsingResult cssParsingResult = cssParsingResultMap.get(method); CssTree cssTree = cssParsingResult.tree; RenamingResult renamingResult = doClassRenaming(cssTree, method, logger, context); // TODO : Should we foresee configuration properties for simplifyCss and eliminateDeadCode // booleans ? ConstantDefinitions constantDefinitions = optimizeTree(cssParsingResult, context, true, true, logger); checkErrors(); Set externalClasses = revertRenamingOfExternalClasses(cssTree, renamingResult); checkErrors(); // Validate that classes not assigned to one of the interface methods are external validateExternalClasses(externalClasses, renamingResult.externalClassCandidate, method, logger); SourceWriter sw = new StringSourceWriter(); sw.println("new " + method.getReturnType().getQualifiedSourceName() + "() {"); sw.indent(); Map actualReplacements = writeMethods(logger, context, method, sw, constantDefinitions, cssParsingResult.originalConstantNameMapping, renamingResult.mapping); sw.outdent(); sw.println("}"); CssResourceGenerator.outputCssMapArtifact(logger, context, method, actualReplacements); return sw.toString(); } private void validateExternalClasses(Set externalClasses, Set externalClassCandidates, JMethod method, TreeLogger logger) throws UnableToCompleteException { if (!isStrictResource(method)) { return; } boolean hasError = false; for (String candidate : externalClassCandidates) { if (!externalClasses.contains(candidate)) { logger.log(Type.ERROR, "The following non-obfuscated class is present in a strict " + "CssResource: " + candidate + ". Fix by adding String accessor " + "method(s) to the CssResource interface for obfuscated classes, " + "or use an @external declaration for unobfuscated classes."); hasError = true; } } if (hasError) { throw new UnableToCompleteException(); } } @Override public void init(TreeLogger logger, ResourceContext context) throws UnableToCompleteException { cssParsingResultMap = new IdentityHashMap<>(); errorManager = new LoggerErrorManager(logger); allowedNonStandardFunctions = new HashSet<>(); allowedAtRules = Sets.newHashSet(ExternalClassesCollector.EXTERNAL_AT_RULE); try { PropertyOracle propertyOracle = context.getGeneratorContext().getPropertyOracle(); ConfigurationProperty styleProp = propertyOracle.getConfigurationProperty(KEY_STYLE); obfuscationStyle = CssObfuscationStyle.getObfuscationStyle(styleProp.getValues().get(0)); obfuscationPrefix = getObfuscationPrefix(propertyOracle, context); ConfigurationProperty allowedAtRuleProperty = propertyOracle .getConfigurationProperty(ALLOWED_AT_RULE); allowedAtRules.addAll(allowedAtRuleProperty.getValues()); ConfigurationProperty allowedFunctionsProperty = propertyOracle .getConfigurationProperty(ALLOWED_FUNCTIONS); allowedNonStandardFunctions.addAll(allowedFunctionsProperty.getValues()); ClientBundleRequirements requirements = context.getRequirements(); requirements.addConfigurationProperty(KEY_STYLE); requirements.addConfigurationProperty(KEY_OBFUSCATION_PREFIX); requirements.addConfigurationProperty(ALLOWED_AT_RULE); requirements.addConfigurationProperty(ALLOWED_FUNCTIONS); requirements.addConfigurationProperty(KEY_CONVERSION_MODE); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Unable to query module property", e); throw new UnableToCompleteException(); } TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle(); JClassType cssResourceInterface = typeOracle.findType(CssResource.class.getCanonicalName()); JClassType resourcePrototypeInterface = typeOracle.findType(ResourcePrototype.class .getCanonicalName()); try { getTextMethod = cssResourceInterface.getMethod("getText", new JType[0]); ensuredInjectedMethod = cssResourceInterface.getMethod("ensureInjected", new JType[0]); getNameMethod = resourcePrototypeInterface.getMethod("getName", new JType[0]); } catch (NotFoundException e) { logger.log(TreeLogger.ERROR, "Unable to lookup methods from CssResource and " + "ResourcePrototype interface", e); throw new UnableToCompleteException(); } initReplacement(context); } @SuppressWarnings("unchecked") private void initReplacement(ResourceContext context) { if (context.getCachedData(KEY_HAS_CACHED_DATA, Boolean.class) != Boolean.TRUE) { context.putCachedData(KEY_SHARED_METHODS, new IdentityHashMap()); context.putCachedData(KEY_BY_CLASS_AND_METHOD, new IdentityHashMap>()); context.putCachedData(KEY_HAS_CACHED_DATA, Boolean.TRUE); } replacementsByClassAndMethod = context.getCachedData(KEY_BY_CLASS_AND_METHOD, Map.class); replacementsForSharedMethods = context.getCachedData(KEY_SHARED_METHODS, Map.class); } private String getObfuscationPrefix(PropertyOracle propertyOracle, ResourceContext context) throws BadPropertyValueException { String prefix = propertyOracle.getConfigurationProperty(KEY_OBFUSCATION_PREFIX) .getValues().get(0); if ("empty".equalsIgnoreCase(prefix)) { return ""; } else if ("default".equalsIgnoreCase(prefix)) { return getDefaultObfuscationPrefix(context); } return prefix; } private String getDefaultObfuscationPrefix(ResourceContext context) { String prefix = context.getCachedData(KEY_CLASS_PREFIX, String.class); if (prefix == null) { prefix = computeDefaultPrefix(context); context.putCachedData(KEY_CLASS_PREFIX, prefix); } return prefix; } private String computeDefaultPrefix(ResourceContext context) { SortedSet gssResources = computeOperableTypes(context); Adler32 checksum = new Adler32(); for (JClassType type : gssResources) { checksum.update(Util.getBytes(type.getQualifiedSourceName())); } int seed = Math.abs((int) checksum.getValue()); return encode(seed) + "-"; } private SortedSet computeOperableTypes(ResourceContext context) { TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle(); JClassType baseInterface = typeOracle.findType(CssResource.class.getCanonicalName()); SortedSet toReturn = new TreeSet<>(new JClassOrderComparator()); JClassType[] cssResourceSubtypes = baseInterface.getSubtypes(); for (JClassType type : cssResourceSubtypes) { if (type.isInterface() != null) { toReturn.add(type); } } return toReturn; } @Override public void prepare(TreeLogger logger, ResourceContext context, ClientBundleRequirements requirements, JMethod method) throws UnableToCompleteException { if (method.getReturnType().isInterface() == null) { logger.log(TreeLogger.ERROR, "Return type must be an interface"); throw new UnableToCompleteException(); } URL[] resourceUrls = findResources(logger, context, method, gssOptions.isEnabled()); if (resourceUrls.length == 0) { logger.log(TreeLogger.ERROR, "At least one source must be specified"); throw new UnableToCompleteException(); } CssParsingResult cssParsingResult = parseResources(Lists.newArrayList(resourceUrls), context, logger); cssParsingResultMap.put(method, cssParsingResult); for (String permutationAxis : cssParsingResult.permutationAxes) { try { context.getRequirements().addPermutationAxis(permutationAxis); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Unknown deferred-binding property " + permutationAxis, e); throw new UnableToCompleteException(); } } } @Override protected String getCssExpression(TreeLogger logger, ResourceContext context, JMethod method) throws UnableToCompleteException { CssTree cssTree = cssParsingResultMap.get(method).tree; String standard = printCssTree(cssTree); // TODO add configuration properties for swapLtrRtlInUrl, swapLeftRightInUrl and // shouldFlipConstantReferences booleans RecordingBidiFlipper recordingBidiFlipper = new RecordingBidiFlipper(cssTree.getMutatingVisitController(), false, false, true); recordingBidiFlipper.runPass(); if (recordingBidiFlipper.nodeFlipped()) { String reversed = printCssTree(cssTree); return LocaleInfo.class.getName() + ".getCurrentLocale().isRTL() ? " + reversed + " : " + standard; } else { return standard; } } private void checkErrors() throws UnableToCompleteException { if (errorManager.hasErrors()) { throw new UnableToCompleteException(); } } private RenamingResult doClassRenaming(CssTree cssTree, JMethod method, TreeLogger logger, ResourceContext context) throws UnableToCompleteException { Map> replacementsWithPrefix = computeReplacements(method, logger, context); RenamingSubstitutionMap substitutionMap = new RenamingSubstitutionMap(replacementsWithPrefix); new CssClassRenaming(cssTree.getMutatingVisitController(), substitutionMap, null).runPass(); Map mapping = replacementsWithPrefix.get(""); mapping = Maps.newHashMap(Maps.filterKeys(mapping, Predicates.in(substitutionMap .getStyleClasses()))); return new RenamingResult(mapping, substitutionMap.getExternalClassCandidates()); } /** * When the tree is fully processed, we can now collect the external classes and revert the * renaming for these classes. We cannot collect the external classes during the original renaming * because some external at-rule could be located inside a conditional block and could be * removed when these blocks are evaluated. */ private Set revertRenamingOfExternalClasses(CssTree cssTree, RenamingResult renamingResult) { ExternalClassesCollector externalClassesCollector = new ExternalClassesCollector(cssTree .getMutatingVisitController(), errorManager); externalClassesCollector.runPass(); Map styleClassesMapping = renamingResult.mapping; // set containing all the style classes before the renaming. Set allStyleClassSet = Sets.newHashSet(styleClassesMapping.keySet()); // add the style classes that aren't associated to a method allStyleClassSet.addAll(renamingResult.externalClassCandidate); Set externalClasses = externalClassesCollector.getExternalClassNames(allStyleClassSet, renamingResult.externalClassCandidate); final Map revertMap = new HashMap<>(externalClasses.size()); for (String external : externalClasses) { revertMap.put(styleClassesMapping.get(external), external); // override the mapping styleClassesMapping.put(external, external); } SubstitutionMap revertExternalClasses = new SubstitutionMap() { @Override public String get(String key) { return revertMap.get(key); } }; new CssClassRenaming(cssTree.getMutatingVisitController(), revertExternalClasses, null) .runPass(); return externalClasses; } private boolean isStrictResource(JMethod method) { NotStrict notStrict = method.getAnnotation(NotStrict.class); return notStrict == null; } private void finalizeTree(CssTree cssTree) throws UnableToCompleteException { new CheckDependencyNodes(cssTree.getMutatingVisitController(), errorManager, false).runPass(); // Don't continue if errors exist checkErrors(); new CreateStandardAtRuleNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateMixins(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateDefinitionNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateConstantReferences(cssTree.getMutatingVisitController()).runPass(); new CreateConditionalNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateRuntimeConditionalNodes(cssTree.getMutatingVisitController()).runPass(); new CreateForLoopNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateComponentNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new ValidatePropertyValues(cssTree.getVisitController(), errorManager).runPass(); new HandleUnknownAtRuleNodes(cssTree.getMutatingVisitController(), errorManager, allowedAtRules, true, false).runPass(); new ProcessKeyframes(cssTree.getMutatingVisitController(), errorManager, true, true).runPass(); new CreateVendorPrefixedKeyframes(cssTree.getMutatingVisitController(), errorManager).runPass(); new UnrollLoops(cssTree.getMutatingVisitController(), errorManager).runPass(); new ProcessRefiners(cssTree.getMutatingVisitController(), errorManager, true).runPass(); new MarkNonFlippableNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); } private ConstantDefinitions optimizeTree(CssParsingResult cssParsingResult, ResourceContext context, boolean simplifyCss, boolean eliminateDeadStyles, TreeLogger logger) throws UnableToCompleteException { CssTree cssTree = cssParsingResult.tree; // Collect mixin definitions and replace mixins CollectMixinDefinitions collectMixinDefinitions = new CollectMixinDefinitions( cssTree.getMutatingVisitController(), errorManager); collectMixinDefinitions.runPass(); new ReplaceMixins(cssTree.getMutatingVisitController(), errorManager, collectMixinDefinitions.getDefinitions()).runPass(); new ProcessComponents<>(cssTree.getMutatingVisitController(), errorManager).runPass(); RuntimeConditionalBlockCollector runtimeConditionalBlockCollector = new RuntimeConditionalBlockCollector(cssTree.getVisitController()); runtimeConditionalBlockCollector.runPass(); Set trueCompileTimeConditions = ImmutableSet.builder() .addAll(getCurrentDeferredBindingProperties(context, cssParsingResult.permutationAxes, logger)) .addAll(getTrueConfigurationProperties(context, cssParsingResult.trueConditions, logger)) .build(); new ExtendedEliminateConditionalNodes(cssTree.getMutatingVisitController(), trueCompileTimeConditions, runtimeConditionalBlockCollector.getRuntimeConditionalBlock()) .runPass(); new ValidateRuntimeConditionalNode(cssTree.getVisitController(), errorManager, gssOptions.isLenientConversion()).runPass(); // Don't continue if errors exist checkErrors(); CollectConstantDefinitions collectConstantDefinitionsPass = new CollectConstantDefinitions( cssTree); collectConstantDefinitionsPass.runPass(); ReplaceConstantReferences replaceConstantReferences = new ReplaceConstantReferences(cssTree, collectConstantDefinitionsPass.getConstantDefinitions(), false, errorManager, false); replaceConstantReferences.runPass(); new ImageSpriteCreator(cssTree.getMutatingVisitController(), context, errorManager).runPass(); Map gssFunctionMap = new GwtGssFunctionMapProvider(context).get(); new ResolveCustomFunctionNodes(cssTree.getMutatingVisitController(), errorManager, gssFunctionMap, true, allowedNonStandardFunctions).runPass(); // collect the final value of the constants and remove them. collectConstantDefinitionsPass = new CollectAndRemoveConstantDefinitions(cssTree); collectConstantDefinitionsPass.runPass(); if (simplifyCss) { // Eliminate empty rules. new EliminateEmptyRulesetNodes(cssTree.getMutatingVisitController()).runPass(); // Eliminating units for zero values. new EliminateUnitsFromZeroNumericValues(cssTree.getMutatingVisitController()).runPass(); // Optimize color values. new ColorValueOptimizer(cssTree.getMutatingVisitController()).runPass(); // Compress redundant top-right-bottom-left value lists. new AbbreviatePositionalValues(cssTree.getMutatingVisitController()).runPass(); } if (eliminateDeadStyles) { // Report errors for duplicate declarations new DisallowDuplicateDeclarations(cssTree.getVisitController(), errorManager).runPass(); // Split rules by selector and declaration. new SplitRulesetNodes(cssTree.getMutatingVisitController()).runPass(); // Dead code elimination. new MarkRemovableRulesetNodes(cssTree).runPass(); new EliminateUselessRulesetNodes(cssTree).runPass(); // Merge of rules with same selector. new MergeAdjacentRulesetNodesWithSameSelector(cssTree).runPass(); new EliminateUselessRulesetNodes(cssTree).runPass(); // Merge of rules with same styles. new MergeAdjacentRulesetNodesWithSameDeclarations(cssTree).runPass(); new EliminateUselessRulesetNodes(cssTree).runPass(); new MarkNonFlippableNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); } return collectConstantDefinitionsPass.getConstantDefinitions(); } private Set getTrueConfigurationProperties(ResourceContext context, Set configurationProperties, TreeLogger logger) throws UnableToCompleteException { Builder setBuilder = ImmutableSet.builder(); PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle(); for (String property : configurationProperties) { try { // TODO : only check configuration properties ? ConfigurationProperty confProp = oracle.getConfigurationProperty(property); if (!checkPropertyIsSingleValueAndBoolean(confProp, logger)) { throw new UnableToCompleteException(); } if ("true".equals(confProp.getValues().get(0))) { setBuilder.add(property); } } catch (BadPropertyValueException e1) { logger.log(Type.ERROR, "Unknown configuration property [" + property + "]"); throw new UnableToCompleteException(); } } return setBuilder.build(); } private Set getCurrentDeferredBindingProperties(ResourceContext context, List permutationAxes, TreeLogger logger) throws UnableToCompleteException { Builder setBuilder = ImmutableSet.builder(); PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle(); for (String permutationAxis : permutationAxes) { String propValue; try { SelectionProperty selProp = oracle.getSelectionProperty(null, permutationAxis); propValue = selProp.getCurrentValue(); } catch (BadPropertyValueException e) { try { ConfigurationProperty confProp = oracle.getConfigurationProperty(permutationAxis); propValue = confProp.getValues().get(0); } catch (BadPropertyValueException e1) { logger.log(Type.ERROR, "Unknown configuration property [" + permutationAxis + "]"); throw new UnableToCompleteException(); } } if (propValue != null) { setBuilder.add(permutationAxis + ":" + propValue); } } return setBuilder.build(); } private CssParsingResult parseResources(List resources, ResourceContext context, TreeLogger logger) throws UnableToCompleteException { List sourceCodes = new ArrayList<>(resources.size()); ImmutableMap.Builder constantNameMappingBuilder = ImmutableMap.builder(); // assert that we only support either gss or css on one resource. boolean css = ensureEitherCssOrGss(resources, logger); if (css && gssOptions.isAutoConversionOff()) { logger.log(Type.ERROR, "Your ClientBundle is referencing css files instead of gss. " + "You will need to either convert these files to gss using the " + "converter tool or turn on auto convertion in your gwt.xml file. " + "Note: Autoconversion will be removed in the next version of GWT, " + "you will need to move to gss." + "Add this line to your gwt.xml file to temporary avoid this:" + " " + "Details on how to migrate to GSS can be found at: http://goo.gl/tEQnmJ"); throw new UnableToCompleteException(); } if (css) { String concatenatedCss = concatCssFiles(resources, logger); ConversionResult result = convertToGss(concatenatedCss, context, logger); if (shouldEmitVariables) { write(result.defNameMapping.keySet()); } String gss = result.gss; String name = "[auto-converted gss files from : " + resources + "]"; sourceCodes.add(new SourceCode(name, gss)); constantNameMappingBuilder.putAll(result.defNameMapping); } else { for (URL stylesheet : resources) { sourceCodes.add(readUrlContent(stylesheet, logger)); } } CssTree tree; try { tree = new GssParser(sourceCodes).parse(); } catch (GssParserException e) { logger.log(TreeLogger.ERROR, "Unable to parse CSS", e); throw new UnableToCompleteException(); } // create more explicit nodes finalizeTree(tree); checkErrors(); // collect boolean conditions that have to be mapped to configuration properties BooleanConditionCollector booleanConditionCollector = new BooleanConditionCollector(tree .getMutatingVisitController()); booleanConditionCollector.runPass(); // collect permutations axis used in conditionals. PermutationsCollector permutationsCollector = new PermutationsCollector(tree .getMutatingVisitController()); permutationsCollector.runPass(); return new CssParsingResult(tree, permutationsCollector.getPermutationAxes(), booleanConditionCollector.getBooleanConditions(), constantNameMappingBuilder.build()); } private static String extractCharset(ByteSource byteSource) throws IOException { String firstLine = byteSource.asCharSource(Charsets.UTF_8).readFirstLine(); if (firstLine != null) { Matcher matcher = CHARSET.matcher(firstLine); if (matcher.matches()) { return matcher.group(1); } } return null; } private ConversionResult convertToGss(String concatenatedCss, ResourceContext context, TreeLogger logger) throws UnableToCompleteException { File tempFile = null; FileOutputStream fos = null; try { // We actually need a URL for the old CssResource to work. So create a temp file. tempFile = File.createTempFile(UUID.randomUUID() + "css_converter", "css.tmp"); fos = new FileOutputStream(tempFile); IOUtils.write(concatenatedCss, fos); fos.close(); ConfigurationPropertyMatcher configurationPropertyMatcher = new ConfigurationPropertyMatcher(context, logger); Css2Gss converter = new Css2Gss(tempFile.toURI().toURL(), logger, gssOptions.isLenientConversion(), configurationPropertyMatcher); String gss = converter.toGss(); if (configurationPropertyMatcher.error) { throw new UnableToCompleteException(); } return new ConversionResult(gss, converter.getDefNameMapping()); } catch (Css2GssConversionException e) { String message = "An error occurs during the automatic conversion: " + e.getMessage(); if (!gssOptions.isLenientConversion()) { message += "\n You should try to change the faulty css to fix this error. If you are " + "unable to change the css, you can setup the automatic conversion to be lenient. Add " + "the following line to your gwt.xml file: " + ""; } logger.log(Type.ERROR, message, e); throw new UnableToCompleteException(); } catch (IOException e) { logger.log(Type.ERROR, "Error while writing temporary css file", e); throw new UnableToCompleteException(); } finally { if (tempFile != null) { tempFile.delete(); } if (fos != null) { IOUtils.closeQuietly(fos); } } } public static String concatCssFiles(List resources, TreeLogger logger) throws UnableToCompleteException { StringBuffer buffer = new StringBuffer(); for (URL stylesheet : resources) { try { String fileContent = Resources.asByteSource(stylesheet).asCharSource(Charsets.UTF_8) .read(); buffer.append(fileContent); buffer.append("\n"); } catch (IOException e) { logger.log(TreeLogger.ERROR, "Unable to parse CSS", e); throw new UnableToCompleteException(); } } return buffer.toString(); } private boolean ensureEitherCssOrGss(List resources, TreeLogger logger) throws UnableToCompleteException { boolean css = resources.get(0).toString().endsWith(".css"); for (URL stylesheet : resources) { if (css && !stylesheet.toString().endsWith(".css")) { logger.log(Type.ERROR, "Only either css files or gss files are supported on one interface"); throw new UnableToCompleteException(); } else if (!css && !stylesheet.toString().endsWith(".gss")) { logger.log(Type.ERROR, "Only either css files or gss files are supported on one interface"); throw new UnableToCompleteException(); } } return css; } private String printCssTree(CssTree tree) { CssPrinter cssPrinterPass = new CssPrinter(tree); cssPrinterPass.runPass(); return cssPrinterPass.getCompactPrintedString(); } private boolean writeClassMethod(TreeLogger logger, JMethod userMethod, Map substitutionMap, SourceWriter sw) throws UnableToCompleteException { if (userMethod.getParameters().length > 0) { logger.log(Type.ERROR, "The method [" + userMethod.getName() + "] shouldn't contain any " + "parameters"); throw new UnableToCompleteException(); } String name = getClassName(userMethod); String value = substitutionMap.get(name); if (value == null) { logger.log(Type.ERROR, "The following style class [" + name + "] is missing from the source" + " CSS file"); return false; } else { writeSimpleGetter(userMethod, "\"" + value + "\"", sw); } return true; } private String getClassName(JMethod method) { String name = method.getName(); ClassName classNameOverride = method.getAnnotation(ClassName.class); if (classNameOverride != null) { name = classNameOverride.value(); } return name; } private boolean writeDefMethod(CssDefinitionNode definitionNode, TreeLogger logger, JMethod userMethod, SourceWriter sw) throws UnableToCompleteException { String name = userMethod.getName(); JClassType classReturnType = userMethod.getReturnType().isClass(); List params = definitionNode.getParameters(); if (params.size() != 1 && !isReturnTypeString(classReturnType)) { logger.log(TreeLogger.ERROR, "@def rule " + name + " must define exactly one value or return type must be String"); return false; } String returnExpr; if (isReturnTypeString(classReturnType)) { List returnValues = new ArrayList(); for (CssValueNode valueNode : params) { returnValues.add(Generator.escape(valueNode.toString())); } returnExpr = "\"" + Joiner.on(" ").join(returnValues) + "\""; } else { JPrimitiveType returnType = userMethod.getReturnType().isPrimitive(); if (returnType == null) { logger.log(TreeLogger.ERROR, name + ": Return type must be primitive type " + "or String for @def accessors"); return false; } CssValueNode valueNode = params.get(0); // when a constant refers to another constant, closure-stylesheet wrap the CssNumericNode in // a CssCompositeValueNode. Unwrap it. if (valueNode instanceof CssCompositeValueNode) { CssCompositeValueNode toUnwrap = (CssCompositeValueNode) valueNode; if (toUnwrap.getValues().size() == 1) { valueNode = toUnwrap.getValues().get(0); } } if (!(valueNode instanceof CssNumericNode)) { logger.log(TreeLogger.ERROR, "The value of the constant defined by @" + name + " is not a" + " numeric"); return false; } String numericValue = ((CssNumericNode) valueNode).getNumericPart(); if (returnType == JPrimitiveType.INT || returnType == JPrimitiveType.LONG) { returnExpr = "" + Long.parseLong(numericValue); } else if (returnType == JPrimitiveType.FLOAT) { returnExpr = numericValue + "F"; } else if (returnType == JPrimitiveType.DOUBLE) { returnExpr = "" + numericValue; } else { logger.log(TreeLogger.ERROR, returnType.getQualifiedSourceName() + " is not a valid primitive return type for @def accessors"); return false; } } writeSimpleGetter(userMethod, returnExpr, sw); return true; } private Map writeMethods(TreeLogger logger, ResourceContext context, JMethod method, SourceWriter sw, ConstantDefinitions constantDefinitions, Map originalConstantNameMapping, Map substitutionMap) throws UnableToCompleteException { JClassType gssResource = method.getReturnType().isInterface(); boolean success = true; Map methodToClassName = new LinkedHashMap<>(); for (JMethod toImplement : gssResource.getOverridableMethods()) { if (toImplement == getTextMethod) { writeGetText(logger, context, method, sw); } else if (toImplement == ensuredInjectedMethod) { writeEnsureInjected(sw); } else if (toImplement == getNameMethod) { writeGetName(method, sw); } else { success &= writeUserMethod(logger, toImplement, sw, constantDefinitions, originalConstantNameMapping, substitutionMap, methodToClassName); } } if (!success) { throw new UnableToCompleteException(); } return methodToClassName; } private boolean writeUserMethod(TreeLogger logger, JMethod userMethod, SourceWriter sw, ConstantDefinitions constantDefinitions, Map originalConstantNameMapping, Map substitutionMap, Map methodToClassName) throws UnableToCompleteException { String className = getClassName(userMethod); // method to access style class ? if (substitutionMap.containsKey(className) && isReturnTypeString(userMethod.getReturnType().isClass())) { methodToClassName.put(userMethod, substitutionMap.get(className)); return writeClassMethod(logger, userMethod, substitutionMap, sw); } // method to access constant value ? CssDefinitionNode definitionNode; String methodName = userMethod.getName(); if (originalConstantNameMapping.containsKey(methodName)) { // method name maps a constant that has been renamed during the auto conversion String constantName = originalConstantNameMapping.get(methodName); definitionNode = constantDefinitions.getConstantDefinition(constantName); } else { definitionNode = constantDefinitions.getConstantDefinition(methodName); if (definitionNode == null) { // try with upper case definitionNode = constantDefinitions.getConstantDefinition(toUpperCase(methodName)); } } if (definitionNode != null) { return writeDefMethod(definitionNode, logger, userMethod, sw); } if (substitutionMap.containsKey(className)) { // method matched a class name but not a constant and the return type is not a string logger.log(Type.ERROR, "The return type of the method [" + userMethod.getName() + "] must " + "be java.lang.String."); throw new UnableToCompleteException(); } // the method doesn't match a style class nor a constant logger.log(Type.ERROR, "The following method [" + userMethod.getName() + "()] doesn't match a constant" + " nor a style class. You could fix that by adding ." + className + " {}" ); return false; } /** * Transform a camel case string to upper case. Each word is separated by a '_' * * @param camelCase */ private String toUpperCase(String camelCase) { return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCase); } private Map> computeReplacements(JMethod method, TreeLogger logger, ResourceContext context) throws UnableToCompleteException { Map> replacementsWithPrefix = new HashMap>(); replacementsWithPrefix .put("", computeReplacementsForType(method.getReturnType().isInterface())); // Process the Import annotation if any Import imp = method.getAnnotation(Import.class); if (imp != null) { boolean fail = false; TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle(); for (Class clazz : imp.value()) { JClassType importType = typeOracle.findType(clazz.getName().replace('$', '.')); assert importType != null : "TypeOracle does not have type " + clazz.getName(); // add this import type as a requirement for this generator context.getRequirements().addTypeHierarchy(importType); String prefix = getImportPrefix(importType); if (replacementsWithPrefix.put(prefix, computeReplacementsForType(importType)) != null) { logger.log(TreeLogger.ERROR, "Multiple imports that would use the prefix " + prefix); fail = true; } } if (fail) { throw new UnableToCompleteException(); } } return replacementsWithPrefix; } private Map computeReplacementsForType(JClassType cssResource) { Map replacements = replacementsByClassAndMethod.get(cssResource); if (replacements == null) { replacements = new HashMap(); replacementsByClassAndMethod.put(cssResource, replacements); String resourcePrefix = resourcePrefixBuilder.get(cssResource.getQualifiedSourceName()); // This substitution map will prefix each renamed class with the resource prefix and use a // MinimalSubstitutionMap for computing the obfuscated name. SubstitutionMap prefixingSubstitutionMap = new PrefixingSubstitutionMap( new MinimalSubstitutionMap(), obfuscationPrefix + resourcePrefix + "-"); for (JMethod method : cssResource.getOverridableMethods()) { if (method == getNameMethod || method == getTextMethod || method == ensuredInjectedMethod) { continue; } String styleClass = getClassName(method); if (replacementsForSharedMethods.containsKey(method)) { replacements.put(styleClass, replacementsForSharedMethods.get(method)); } else { String obfuscatedClassName = prefixingSubstitutionMap.get(styleClass); String replacement = obfuscationStyle.getPrettyName(styleClass, cssResource, obfuscatedClassName); if (hasSharedAnnotation(method)) { // We always use the base type for obfuscation if this is a shared method replacement = obfuscationStyle.getPrettyName(styleClass, method.getEnclosingType(), obfuscatedClassName); replacementsForSharedMethods.put(method, replacement); } replacements.put(styleClass, replacement); } } } return replacements; } private boolean hasSharedAnnotation(JMethod method) { JClassType enclosingType = method.getEnclosingType(); Shared shared = enclosingType.getAnnotation(Shared.class); return shared != null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy