blob: 2f049a3ccbb43a4cce4669934a1873adbc7f8d5a [file] [log] [blame]
/*
* 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.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
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.dev.util.xml.ReflectiveParser;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.collect.MapMaker;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.util.tools.Utility;
import java.io.File;
import java.io.Reader;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* The top-level API for loading module XML.
*/
public class ModuleDefLoader {
/*
* TODO(scottb,tobyr,zundel): synchronization????
*/
/**
* Filename suffix used for GWT Module XML files.
*/
public static final String GWT_MODULE_XML_SUFFIX = ".gwt.xml";
/**
* Keep soft references to loaded modules so the VM can gc them when memory is
* tight. The current context class loader used as a key for modules cache.
* The module's physical name is used as a key inside the cache.
*/
private static final Map<ClassLoader, Map<String, ModuleDef>> loadedModulesCaches =
new MapMaker().weakKeys().makeMap();
/**
* A mapping from effective to physical module names.
*/
private static final Map<String, String> moduleEffectiveNameToPhysicalName =
new HashMap<String, String>();
public static void clearModuleCache() {
getModulesCache().clear();
}
/**
* Creates a module in memory that is not associated with a
* <code>.gwt.xml</code> file on disk.
*
* @param logger logs the process
* @param moduleName the synthetic module to create
* @param inherits a set of modules to inherit from
* @param refresh whether to refresh the module
* @return the loaded module
* @throws UnableToCompleteException
*/
public static ModuleDef createSyntheticModule(TreeLogger logger, String moduleName,
final String[] inherits, boolean refresh)
throws UnableToCompleteException {
ModuleDef moduleDef = tryGetLoadedModule(moduleName, refresh);
if (moduleDef != null) {
return moduleDef;
}
ResourceLoader resources = ResourceLoaders.fromContextClassLoader();
ModuleDefLoader loader = new ModuleDefLoader(resources) {
@Override
protected void load(TreeLogger logger, String nameOfModuleToLoad, ModuleDef dest)
throws UnableToCompleteException {
logger.log(TreeLogger.TRACE, "Loading module '" + nameOfModuleToLoad + "'");
for (String inherit : inherits) {
nestedLoad(logger, inherit, dest);
}
}
};
ModuleDef module = doLoadModule(loader, logger, moduleName, resources);
/*
* Must reset name override on synthetic modules. Otherwise they'll be
* incorrectly affected by the last inherits tag, because they have no XML
* which would reset the name at the end of parse.
*/
module.setNameOverride(null);
return module;
}
/**
* Loads a new module from the class path and defers scanning associated directories for
* resources.
*/
public static ModuleDef loadFromClassPath(TreeLogger logger, String moduleName)
throws UnableToCompleteException {
return loadFromClassPath(logger, moduleName, false);
}
/**
* Loads a new module from the class path and may or may not immediately scan associated
* directories for resources.
*/
public static ModuleDef loadFromClassPath(TreeLogger logger, String moduleName, boolean refresh)
throws UnableToCompleteException {
ResourceLoader resources = ResourceLoaders.fromContextClassLoader();
return loadFromResources(logger, moduleName, resources, refresh);
}
/**
* Loads a new module from the given ResourceLoader and may or may not immediately scan associated
* directories for resources.
*/
public static ModuleDef loadFromResources(TreeLogger logger, String moduleName,
ResourceLoader resources, boolean refresh)
throws UnableToCompleteException {
Event moduleDefLoadFromClassPathEvent = SpeedTracerLogger.start(
CompilerEventType.MODULE_DEF, "phase", "loadFromClassPath", "moduleName", moduleName);
try {
// Look up the module's physical name; if null, we are either encountering
// the module for the first time, or else the name is already physical
String physicalName = moduleEffectiveNameToPhysicalName.get(moduleName);
if (physicalName != null) {
moduleName = physicalName;
}
ModuleDef moduleDef = tryGetLoadedModule(moduleName, refresh);
if (moduleDef != null) {
return moduleDef;
}
ModuleDefLoader loader = new ModuleDefLoader(resources);
return ModuleDefLoader.doLoadModule(loader, logger, moduleName, resources, true, true);
} finally {
moduleDefLoadFromClassPathEvent.end();
}
}
/**
* This method loads a module while assuming it is monolithic.
*
* @param loader the loader to use
* @param logger used to log the loading process
* @param moduleName the name of the module
* @param resources where to load source code from
* @return the module returned -- cannot be null
* @throws UnableToCompleteException if module loading failed
*/
private static ModuleDef doLoadModule(ModuleDefLoader loader, TreeLogger logger,
String moduleName, ResourceLoader resources)
throws UnableToCompleteException {
return doLoadModule(loader, logger, moduleName, resources, true, true);
}
/**
* This method loads a module.
*
* @param loader the loader to use
* @param logger used to log the loading process
* @param moduleName the name of the module
* @param resources where to load source code from
* @param monolithic whether to encapsulate the entire module tree
* @param mergePathPrefixes whether PathPrefixSets should merge colliding PathPrefixes for faster
* resource scanning
* @return the module returned -- cannot be null
* @throws UnableToCompleteException if module loading failed
*/
private static ModuleDef doLoadModule(ModuleDefLoader loader, TreeLogger logger,
String moduleName, ResourceLoader resources, boolean monolithic, boolean mergePathPrefixes)
throws UnableToCompleteException {
ModuleDef moduleDef = new ModuleDef(moduleName, resources, monolithic, mergePathPrefixes);
Event moduleLoadEvent = SpeedTracerLogger.start(CompilerEventType.MODULE_DEF,
"phase", "strategy.load()");
loader.load(logger, moduleName, moduleDef);
moduleLoadEvent.end();
// Do any final setup.
//
Event moduleNormalizeEvent = SpeedTracerLogger.start(CompilerEventType.MODULE_DEF,
"phase", "moduleDef.normalize()");
moduleDef.normalize(logger);
moduleNormalizeEvent.end();
// Add the "physical" module name: com.google.Module
getModulesCache().put(moduleName, moduleDef);
// Add a mapping from the module's effective name to its physical name
moduleEffectiveNameToPhysicalName.put(moduleDef.getName(), moduleName);
return moduleDef;
}
static Map<String, ModuleDef> getModulesCache() {
ClassLoader keyClassLoader = Thread.currentThread().getContextClassLoader();
Map<String, ModuleDef> cache = loadedModulesCaches.get(keyClassLoader);
if (cache == null) {
cache = Maps.newHashMap();
loadedModulesCaches.put(keyClassLoader, cache);
}
return cache;
}
private static ModuleDef tryGetLoadedModule(String moduleName, boolean refresh) {
ModuleDef moduleDef = getModulesCache().get(moduleName);
if (moduleDef == null || moduleDef.isGwtXmlFileStale()) {
return null;
} else if (refresh) {
moduleDef.refresh();
}
return moduleDef;
}
private final ResourceLoader resourceLoader;
private ModuleDefLoader(ResourceLoader loader) {
this.resourceLoader = loader;
}
/**
* Loads a module and all its included modules, recursively, into the given ModuleDef.
* @throws UnableToCompleteException
*/
protected void load(TreeLogger logger, String nameOfModuleToLoad, ModuleDef dest)
throws UnableToCompleteException {
nestedLoad(logger, nameOfModuleToLoad, dest);
}
/**
* Loads a new module and its descendants into <code>moduleDef</code> as included modules.
* (If there are any descendants, this method will be called recursively.)
*
* @param parentLogger Logs the process.
* @param moduleName The module to load.
* @param moduleDef The module to add the new module to.
* @throws UnableToCompleteException
*/
void nestedLoad(TreeLogger parentLogger, String moduleName, ModuleDef moduleDef)
throws UnableToCompleteException {
if (moduleDef.isInherited(moduleName)) {
// No need to parse module again.
return;
}
TreeLogger logger = parentLogger.branch(TreeLogger.DEBUG, "Loading inherited module '"
+ moduleName + "'", null);
if (!ModuleDef.isValidModuleName(moduleName)) {
logger.log(TreeLogger.ERROR, "Invalid module name: '" + moduleName + "'",
null);
throw new UnableToCompleteException();
}
moduleDef.addInheritedModules(moduleName);
// Find the specified module using the classpath.
//
String slashedModuleName = moduleName.replace('.', '/');
String resName = slashedModuleName + ModuleDefLoader.GWT_MODULE_XML_SUFFIX;
URL moduleURL = resourceLoader.getResource(resName);
if (moduleURL != null) {
moduleDef.recordModuleGwtXmlFile(moduleName, moduleURL.getPath());
String externalForm = moduleURL.toExternalForm();
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG, "Module location: " + externalForm, null);
}
try {
if ((!(externalForm.startsWith("jar:file")))
&& (!(externalForm.startsWith("zip:file")))
&& (!(externalForm.startsWith("http://")))
&& (!(externalForm.startsWith("ftp://")))) {
moduleDef.addGwtXmlFile(new File(moduleURL.toURI()));
}
} catch (URISyntaxException e) {
logger.log(TreeLogger.ERROR, "Error parsing URI", e);
throw new UnableToCompleteException();
}
}
if (moduleURL == null) {
logger.log(TreeLogger.ERROR, formatUnableToFindModuleMessage(resName));
throw new UnableToCompleteException();
}
// Extract just the directory containing the module.
//
String moduleDir = "";
int i = slashedModuleName.lastIndexOf('/');
if (i != -1) {
moduleDir = slashedModuleName.substring(0, i) + "/";
}
// Parse it.
//
Reader r = null;
try {
r = Util.createReader(logger, moduleURL);
ModuleDefSchema schema =
new ModuleDefSchema(logger, this, moduleName, moduleURL, moduleDir, moduleDef);
ReflectiveParser.parse(logger, schema, r);
} catch (UnableToCompleteException e) {
// The error has already been logged.
throw e;
} catch (Throwable e) {
logger.log(TreeLogger.ERROR, "Unexpected error while processing XML", e);
throw new UnableToCompleteException();
} finally {
Utility.close(r);
}
}
@VisibleForTesting
public static String formatUnableToFindModuleMessage(String moduleResourcePath) {
return "Unable to find '" + moduleResourcePath + "' on your classpath; "
+ "could be a typo, or maybe you forgot to include a classpath entry for source?";
}
}