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