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

com.google.gwt.dev.cfg.ModuleDef Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.dev.cfg;

import com.google.gwt.core.ext.Linker;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.javac.CompilationProblemReporter;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.CompilationStateBuilder;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.resource.ResourceOracle;
import com.google.gwt.dev.resource.impl.DefaultFilters;
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.Empty;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.base.Charsets;
import com.google.gwt.thirdparty.guava.common.base.Predicates;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.Iterators;
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 java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Represents a module specification. In principle, this could be built without
 * XML for unit tests.
 */
public class ModuleDef implements DepsInfoProvider {
  private static final ResourceFilter NON_JAVA_RESOURCES = new ResourceFilter() {
    @Override
    public boolean allows(String path) {
      return !path.endsWith(".java") && !path.endsWith(".class");
    }
  };

  private static final Comparator> REV_NAME_CMP =
      new Comparator>() {
        @Override
        public int compare(Map.Entry entry1, Map.Entry entry2) {
          String key1 = entry1.getKey();
          String key2 = entry2.getKey();
          // intentionally reversed
          return key2.compareTo(key1);
        }
      };

  public static boolean isValidModuleName(String moduleName) {
    // Check for an empty string between two periods.
    if (moduleName.contains("..")) {
      return false;
    }
    // Insure the package name components are a valid Java ident.
    String[] parts = moduleName.split("\\.");
    for (int i = 0; i < parts.length - 1; i++) {
      String part = parts[i];
      if (!Util.isValidJavaIdent(part)) {
        return false;
      }
    }
    return true;
  }

  /**
   * All resources found on the public path, specified by  directives in
   * modules (or the implicit ./public directory). Marked 'lazy' because it does not
   * start searching for resources until a query is made.
   */
  protected ResourceOracleImpl lazyPublicOracle;

  private final Set activeLinkers = new LinkedHashSet();

  private String activePrimaryLinker;

  private boolean collapseAllProperties;

  private final DefaultFilters defaultFilters;

  private final List entryPointTypeNames = new ArrayList();

  /**
   * A stack that keeps track of the name of the current module in the enterModule/exitModule tree.
   */
  private final Deque currentLibraryModuleNames = Lists.newLinkedList();

  private final Set gwtXmlFiles = new HashSet();

  private final Set inheritedModules = new HashSet();

  /**
   * Contains files other than .java and .class files (such as CSS and PNG files) from either the
   * source or resource paths.
   */
  private ResourceOracleImpl lazyResourcesOracle;

  /**
   * Contains all files from the source path, specified by  and 
   * directives in modules (or the implicit ./client directory).
   */
  private ResourceOracleImpl lazySourceOracle;

  private final Map> linkerTypesByName =
      new LinkedHashMap>();

  private final long moduleDefCreationTime = System.currentTimeMillis();

  private final String name;

  /**
   * Must use a separate field to track override, because setNameOverride() will
   * get called every time a module is inherited, but only the last one matters.
   */
  private String nameOverride;

  private final Properties properties = new Properties();

  private PathPrefixSet publicPrefixSet = new PathPrefixSet();

  private Set resourcePrefixes = Sets.newHashSet();

  private final ResourceLoader resources;

  private boolean resourcesScanned;

  private final Deque rules = Lists.newLinkedList();

  private final Scripts scripts = new Scripts();

  private final Map servletClassNamesByPath = new HashMap();

  private final PathPrefixSet sourcePrefixSet;

  private final Styles styles = new Styles();

  /**
   * A mapping from names of modules to path to their associated .gwt.xml file.
   */
  private Map gwtXmlPathByModuleName = Maps.newHashMap();

  public ModuleDef(String name) {
    this(name, ResourceLoaders.fromContextClassLoader());
  }

  public ModuleDef(String name, ResourceLoader resources) {
    this(name, resources, true, true);
  }

  /**
   * Constructs a ModuleDef.
   */
  public ModuleDef(String name, ResourceLoader resources, boolean monolithic,
      boolean mergePathPrefixes) {
    this.name = name;
    this.resources = resources;
    sourcePrefixSet = new PathPrefixSet(mergePathPrefixes);
    defaultFilters = new DefaultFilters();
  }

  public synchronized void addEntryPointTypeName(String typeName) {
    entryPointTypeNames.add(typeName);
  }

  public void addGwtXmlFile(File xmlFile) {
    gwtXmlFiles.add(xmlFile);
  }

  public void addLinker(String name) {
    Class clazz = getLinker(name);
    assert clazz != null;

    LinkerOrder order = clazz.getAnnotation(LinkerOrder.class);
    if (order.value() == Order.PRIMARY) {
      if (activePrimaryLinker != null) {
        activeLinkers.remove(activePrimaryLinker);
      }
      activePrimaryLinker = name;
    }

    activeLinkers.add(name);
  }

  public synchronized void addPublicPackage(String publicPackage, String[] includeList,
      String[] excludeList, String[] skipList, boolean defaultExcludes, boolean caseSensitive) {
    if (lazyPublicOracle != null) {
      throw new IllegalStateException("Already normalized");
    }
    publicPrefixSet.add(new PathPrefix(getCurrentLibraryModuleName(), publicPackage, defaultFilters
        .customResourceFilter(includeList, excludeList, skipList, defaultExcludes, caseSensitive),
        true, excludeList));
  }

  public void addResourcePath(String resourcePath) {
    if (lazyResourcesOracle != null) {
      throw new IllegalStateException("Already normalized");
    }
    resourcePrefixes.add(new PathPrefix(getCurrentLibraryModuleName(), resourcePath,
        NON_JAVA_RESOURCES, false, null));
  }

  public void addRule(Rule rule) {
    getRules().addFirst(rule);
  }

  public void addSourcePackage(String sourcePackage, String[] includeList, String[] excludeList,
      String[] skipList, boolean defaultExcludes, boolean caseSensitive) {
    addSourcePackageImpl(sourcePackage, includeList, excludeList, skipList, defaultExcludes,
        caseSensitive, false);
  }

  public void addSourcePackageImpl(String sourcePackage, String[] includeList,
      String[] excludeList, String[] skipList, boolean defaultExcludes, boolean caseSensitive,
      boolean isSuperSource) {
    if (lazySourceOracle != null) {
      throw new IllegalStateException("Already normalized");
    }
    PathPrefix pathPrefix = new PathPrefix(getCurrentLibraryModuleName(),
        sourcePackage, defaultFilters.customJavaFilter(includeList, excludeList, skipList,
            defaultExcludes, caseSensitive), isSuperSource, excludeList);
    sourcePrefixSet.add(pathPrefix);
  }

  public void addSuperSourcePackage(String superSourcePackage, String[] includeList,
      String[] excludeList, String[] skipList, boolean defaultExcludes, boolean caseSensitive) {
    addSourcePackageImpl(superSourcePackage, includeList, excludeList, skipList, defaultExcludes,
        caseSensitive, true);
  }

  public void clearEntryPoints() {
    entryPointTypeNames.clear();
  }

  /**
   * Deactivate a linker by name. Returns false if the linker was not active, true otherwise.
   */
  public boolean deactivateLinker(String linkerName) {
    return activeLinkers.remove(linkerName);
  }

  /**
   * Associate a Linker class with a symbolic name. If the name had been
   * previously assigned, this method will redefine the name. If the redefined
   * linker had been previously added to the set of active linkers, the old
   * active linker will be replaced with the new linker.
   */
  public void defineLinker(TreeLogger logger, String name, Class linker)
      throws UnableToCompleteException {
    Class old = getLinker(name);
    if (old != null) {
      // Redefining an existing name
      if (activePrimaryLinker.equals(name)) {
        // Make sure the new one is also a primary linker
        if (!linker.getAnnotation(LinkerOrder.class).value().equals(Order.PRIMARY)) {
          logger.log(TreeLogger.ERROR, "Redefining primary linker " + name
              + " with non-primary implementation " + linker.getName());
          throw new UnableToCompleteException();
        }

      } else if (activeLinkers.contains(name)) {
        // Make sure it's a not a primary linker
        if (linker.getAnnotation(LinkerOrder.class).value().equals(Order.PRIMARY)) {
          logger.log(TreeLogger.ERROR, "Redefining non-primary linker " + name
              + " with primary implementation " + linker.getName());
          throw new UnableToCompleteException();
        }
      }
    }
    linkerTypesByName.put(name, linker);
  }

  /**
   * Called as module tree parsing enters and exits individual module file of various types and so
   * provides the ModuleDef with an opportunity to keep track of what context it is currently
   * operating.
   */
  public void enterModule(String canonicalModuleName) {
    currentLibraryModuleNames.push(canonicalModuleName);
  }

  public void exitModule() {
    currentLibraryModuleNames.pop();
  }

  public synchronized Resource findPublicFile(String partialPath) {
    ensureResourcesScanned();
    return lazyPublicOracle.getResource(partialPath);
  }

  public synchronized String findServletForPath(String actual) {
    // Walk in backwards sorted order to find the longest path match first.
    Set> entrySet = servletClassNamesByPath.entrySet();
    Entry[] entries = Util.toArray(Entry.class, entrySet);
    Arrays.sort(entries, REV_NAME_CMP);
    for (int i = 0, n = entries.length; i < n; ++i) {
      String mapping = entries[i].getKey();
      /*
       * Ensure that URLs that match the servlet mapping, including those that
       * have additional path_info, get routed to the correct servlet.
       *
       * See "Inside Servlets", Second Edition, pg. 208
       */
      if (actual.equals(mapping) || actual.startsWith(mapping + "/")) {
        return entries[i].getValue();
      }
    }
    return null;
  }

  /**
   * Returns the Resource for a source file if it is found; null
   * otherwise.
   *
   * @param partialPath the partial path of the source file
   * @return the resource for the requested source file
   */
  public synchronized Resource findSourceFile(String partialPath) {
    ensureResourcesScanned();
    return lazySourceOracle.getResource(partialPath);
  }

  public Set getActiveLinkerNames() {
    return new LinkedHashSet(activeLinkers);
  }

  public Set> getActiveLinkers() {
    Set> toReturn = new LinkedHashSet>();
    for (String linker : activeLinkers) {
      assert linkerTypesByName.containsKey(linker) : linker;
      toReturn.add(linkerTypesByName.get(linker));
    }
    return toReturn;
  }

  public Class getActivePrimaryLinker() {
    assert linkerTypesByName.containsKey(activePrimaryLinker) : activePrimaryLinker;
    return linkerTypesByName.get(activePrimaryLinker);
  }

  /**
   * Strictly for statistics gathering. There is no guarantee that the source
   * oracle has been initialized.
   */
  public String[] getAllSourceFiles() {
    ensureResourcesScanned();
    return lazySourceOracle.getPathNames().toArray(Empty.STRINGS);
  }

  public synchronized ResourceOracle getBuildResourceOracle() {
    if (lazyResourcesOracle == null) {
      lazyResourcesOracle = new ResourceOracleImpl(TreeLogger.NULL, resources);
      PathPrefixSet pathPrefixes = lazySourceOracle.getPathPrefixes();

      // For backwards compatibility, register source resource paths as build resource paths as
      // well.
      PathPrefixSet newPathPrefixes = new PathPrefixSet();
      for (PathPrefix pathPrefix : pathPrefixes.values()) {
        newPathPrefixes.add(new PathPrefix(pathPrefix.getModuleName(), pathPrefix.getPrefix(),
            NON_JAVA_RESOURCES, pathPrefix.shouldReroot(), null));
      }

      // Register build resource paths.
      for (PathPrefix resourcePathPrefix : resourcePrefixes) {
        newPathPrefixes.add(resourcePathPrefix);
      }
      lazyResourcesOracle.setPathPrefixes(newPathPrefixes);
      lazyResourcesOracle.scanResources(TreeLogger.NULL);
    } else {
      ensureResourcesScanned();
    }
    return lazyResourcesOracle;
  }

  /**
   * Returns the physical name for the module by which it can be found in the
   * classpath.
   */
  public String getCanonicalName() {
    return name;
  }

  public CompilationState getCompilationState(TreeLogger logger, CompilerContext compilerContext)
      throws UnableToCompleteException {
    ensureResourcesScanned();
    CompilationState compilationState = CompilationStateBuilder.buildFrom(
        logger, compilerContext, compilerContext.getSourceResourceOracle().getResources());
    checkForSeedTypes(logger, compilationState);
    return compilationState;
  }

  public synchronized String[] getEntryPointTypeNames() {
    final int n = entryPointTypeNames.size();
    return entryPointTypeNames.toArray(new String[n]);
  }

  public synchronized String getFunctionName() {
    return getName().replace('.', '_');
  }

  public List getGeneratorRules() {
    return ImmutableList.copyOf(
        Iterators.filter(rules.iterator(), Predicates.instanceOf(RuleGenerateWith.class)));
  }

  @Override
  public String getGwtXmlFilePath(String moduleName) {
    return gwtXmlPathByModuleName.get(moduleName);
  }

  /**
   * Calculates a hash of the filenames of all the input files for the GWT compiler.
   *
   * 

This is needed because the list of files could change without affecting the timestamp. * For example, consider a glob that matches fewer files than before because a file was * deleted. */ public int getInputFilenameHash() { List filenames = new ArrayList(); filenames.addAll(gwtXmlPathByModuleName.values()); for (Resource resource : getResourcesNewerThan(Integer.MIN_VALUE)) { filenames.add(resource.getLocation()); } // Take the first four bytes of the SHA-1 hash. Collections.sort(filenames); MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("SHA-1 unavailable", e); } for (String filename : filenames) { digest.update(filename.getBytes(Charsets.UTF_8)); } byte[] bytes = digest.digest(); return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; } public Class getLinker(String name) { return linkerTypesByName.get(name); } public Map> getLinkers() { return linkerTypesByName; } public synchronized String getName() { return nameOverride != null ? nameOverride : name; } /** * The properties that have been defined. */ public synchronized Properties getProperties() { return properties; } public ResourceOracleImpl getPublicResourceOracle() { ensureResourcesScanned(); return lazyPublicOracle; } public long getResourceLastModified() { long resourceLastModified = 1000; Set allResources = Sets.newHashSet(); allResources.addAll(getPublicResourceOracle().getResources()); allResources.addAll(getSourceResourceOracle().getResources()); allResources.addAll(getBuildResourceOracle().getResources()); for (Resource resource : allResources) { resourceLastModified = Math.max(resourceLastModified, resource.getLastModified()); } return resourceLastModified; } public Set getResourcesNewerThan(long modificationTime) { Set newerResources = Sets.newHashSet(); for (Resource resource : getPublicResourceOracle().getResources()) { if (resource.getLastModified() > modificationTime) { newerResources.add(resource); } } for (Resource resource : getSourceResourceOracle().getResources()) { if (resource.getLastModified() > modificationTime) { newerResources.add(resource); } } for (Resource resource : getBuildResourceOracle().getResources()) { if (resource.getLastModified() > modificationTime) { newerResources.add(resource); } } return newerResources; } /** * Gets a reference to the internal rules for this module def. */ public synchronized Deque getRules() { return rules; } /** * Gets a reference to the internal scripts list for this module def. */ public Scripts getScripts() { return scripts; } public synchronized String[] getServletPaths() { return servletClassNamesByPath.keySet().toArray(Empty.STRINGS); } @Override public Set getSourceModuleNames(String typeSourceName) { ensureResourcesScanned(); return lazySourceOracle.getSourceModulesByTypeSourceName().get(typeSourceName); } public synchronized ResourceOracle getSourceResourceOracle() { ensureResourcesScanned(); return lazySourceOracle; } /** * Gets a reference to the internal styles list for this module def. */ public Styles getStyles() { return styles; } public boolean isGwtXmlFileStale() { return lastModified() > moduleDefCreationTime; } public boolean isInherited(String moduleName) { return inheritedModules.contains(moduleName); } public long lastModified() { long lastModified = 0; for (File xmlFile : gwtXmlFiles) { if (xmlFile.exists()) { lastModified = Math.max(lastModified, xmlFile.lastModified()); } } return lastModified > 0 ? lastModified : moduleDefCreationTime; } /** * For convenience in unit tests, servlets can be automatically loaded and * mapped in the embedded web server. If a servlet is already mapped to the * specified path, it is replaced. * * @param path the url path at which the servlet resides * @param servletClassName the name of the servlet to publish */ public synchronized void mapServlet(String path, String servletClassName) { servletClassNamesByPath.put(path, servletClassName); } public void recordModuleGwtXmlFile(String moduleName, String fileSourcePath) { gwtXmlPathByModuleName.put(moduleName, fileSourcePath); } public synchronized void refresh() { resourcesScanned = false; } /** * Mainly for testing and decreasing compile times. */ public void setCollapseAllProperties(boolean collapse) { collapseAllProperties = collapse; } /** * Override the module's apparent name. Setting this value to * nullwill disable the name override. */ public synchronized void setNameOverride(String nameOverride) { this.nameOverride = nameOverride; } void addBindingPropertyDefinedValue(BindingProperty bindingProperty, String token) { bindingProperty.addDefinedValue(bindingProperty.getRootCondition(), token); } void addConfigurationPropertyValue(ConfigurationProperty configurationProperty, String value) { configurationProperty.addValue(value); } void addInheritedModules(String moduleName) { inheritedModules.add(moduleName); } /** * The final method to call when everything is setup. Before calling this * method, several of the getter methods may not be called. After calling this * method, the add methods may not be called. * * @param logger Logs the activity. */ synchronized void normalize(TreeLogger logger) { Event moduleDefNormalize = SpeedTracerLogger.start(CompilerEventType.MODULE_DEF, "phase", "normalize"); // Normalize property providers. // for (BindingProperty prop : properties.getBindingProperties()) { if (collapseAllProperties) { prop.addCollapsedValues("*"); } prop.normalizeCollapsedValues(); /* * Create a default property provider for any properties with more than * one possible value and no existing provider. */ if (prop.getProvider() == null && prop.getConstrainedValue() == null) { String src = "{"; src += "return __gwt_getMetaProperty(\""; src += prop.getName(); src += "\"); }"; prop.setProvider(new PropertyProvider(src)); } } // Create the public path. TreeLogger branch = Messages.PUBLIC_PATH_LOCATIONS.branch(logger, null); lazyPublicOracle = new ResourceOracleImpl(branch, resources); lazyPublicOracle.setPathPrefixes(publicPrefixSet); // Create the source path. branch = Messages.SOURCE_PATH_LOCATIONS.branch(logger, null); lazySourceOracle = new ResourceOracleImpl(branch, resources); lazySourceOracle.setPathPrefixes(sourcePrefixSet); moduleDefNormalize.end(); } void setConfigurationPropertyValue(ConfigurationProperty configurationProperty, String value) { configurationProperty.setValue(value); } private void checkForSeedTypes(TreeLogger logger, CompilationState compilationState) throws UnableToCompleteException { // Sanity check the seed types and don't even start it they're missing. boolean seedTypesMissing = false; TypeOracle typeOracle = compilationState.getTypeOracle(); if (typeOracle.findType("java.lang.Object") == null) { CompilationProblemReporter.logErrorTrace(logger, TreeLogger.ERROR, compilationState.getCompilerContext(), "java.lang.Object", true); seedTypesMissing = true; } else { TreeLogger branch = logger.branch(TreeLogger.TRACE, "Finding entry point classes", null); String[] typeNames = getEntryPointTypeNames(); for (int i = 0; i < typeNames.length; i++) { String typeName = typeNames[i]; if (typeOracle.findType(typeName) == null) { CompilationProblemReporter.logErrorTrace(branch, TreeLogger.ERROR, compilationState.getCompilerContext(), typeName, true); seedTypesMissing = true; } } } if (seedTypesMissing) { throw new UnableToCompleteException(); } } private synchronized void ensureResourcesScanned() { if (resourcesScanned) { return; } resourcesScanned = true; Event moduleDefEvent = SpeedTracerLogger.start( CompilerEventType.MODULE_DEF, "phase", "refresh", "module", getName()); if (lazyResourcesOracle != null) { lazyResourcesOracle.scanResources(TreeLogger.NULL); } lazyPublicOracle.scanResources(TreeLogger.NULL); lazySourceOracle.scanResources(TreeLogger.NULL); moduleDefEvent.end(); } private String getCurrentLibraryModuleName() { return currentLibraryModuleNames.isEmpty() ? "" : currentLibraryModuleNames.peek(); } }