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

com.google.gwt.resources.ext.ResourceGeneratorUtil 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.resources.ext;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.GeneratorContext;
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.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
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.dev.resource.Resource;
import com.google.gwt.dev.resource.ResourceOracle;
import com.google.gwt.dev.util.collect.Maps;
import com.google.gwt.resources.client.ClientBundle.Source;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Utility methods for building ResourceGenerators.
 */
public final class ResourceGeneratorUtil {

  private static class ClassLoaderLocator implements Locator {
    private final ClassLoader classLoader;

    public ClassLoaderLocator(ClassLoader classLoader) {
      this.classLoader = classLoader;
    }

    public URL locate(String resourceName) {
      return classLoader.getResource(resourceName);
    }
  }

  /**
   * A locator which will use files published via
   * {@link ResourceGeneratorUtil#addNamedFile(String, File)}.
   */
  private static class NamedFileLocator implements Locator {
    public static final NamedFileLocator INSTANCE = new NamedFileLocator();

    private NamedFileLocator() {
    }

    public URL locate(String resourceName) {
      File f = namedFiles.get(resourceName);
      if (f != null && f.isFile() && f.canRead()) {
        try {
          return f.toURI().toURL();
        } catch (MalformedURLException e) {
          throw new RuntimeException("Unable to make a URL for file "
              + f.getName());
        }
      }
      return null;
    }
  }

  /**
   * Wrapper interface around different strategies for loading resource data.
   */
  private interface Locator {
    URL locate(String resourceName);
  }

  private static class ResourceOracleLocator implements Locator {
    private final Map resourceMap;

    public ResourceOracleLocator(ResourceOracle oracle) {
      resourceMap = oracle.getResourceMap();
    }

    @SuppressWarnings("deprecation")
    public URL locate(String resourceName) {
      Resource r = resourceMap.get(resourceName);
      return (r == null) ? null : r.getURL();
    }
  }

  private static Map namedFiles = Maps.create();

  /**
   * These are type names from previous APIs or from APIs with similar
   * functionality that might be confusing.
   *
   * @see #checkForDeprecatedAnnotations
   */
  private static final String[] DEPRECATED_ANNOTATION_NAMES = {
      "com.google.gwt.libideas.resources.client.ImmutableResourceBundle$Resource",
      "com.google.gwt.user.client.ui.ImageBundle$Resource"};

  private static final List> DEPRECATED_ANNOTATION_CLASSES;

  static {
    List> classes = new ArrayList>(
        DEPRECATED_ANNOTATION_NAMES.length);

    for (String name : DEPRECATED_ANNOTATION_NAMES) {
      try {
        Class maybeAnnotation = Class.forName(name, false,
            ResourceGeneratorUtil.class.getClassLoader());

        // Possibly throws ClassCastException
        Class annotationClass = maybeAnnotation.asSubclass(Annotation.class);

        classes.add(annotationClass);

      } catch (ClassCastException e) {
        // If it's not an Annotation type, we don't care about it
      } catch (ClassNotFoundException e) {
        // This is OK; the annotation doesn't exist.
      }
    }

    if (classes.isEmpty()) {
      DEPRECATED_ANNOTATION_CLASSES = Collections.emptyList();
    } else {
      DEPRECATED_ANNOTATION_CLASSES = Collections.unmodifiableList(classes);
    }
  }

  /**
   * Publish or override resources named by {@link Source} annotations. This
   * method is intended to be called by Generators that create ClientBundle
   * instances and need to pass source data to the ClientBundle system that is
   * not accessible through the classpath.
   *
   * @param resourceName the path at which the contents of file
   *          should be made available
   * @param file the File whose contents are to be provided to the ClientBundle
   *          system
   */
  public static void addNamedFile(String resourceName, File file) {
    assert resourceName != null : "resourceName";
    assert file != null : "file";
    assert file.isFile() && file.canRead() : "file does not exist or cannot be read";

    namedFiles = Maps.put(namedFiles, resourceName, file);
  }

  /**
   * Returns the base filename of a resource. The behavior is similar to the unix
   * command basename.
   *
   * @param resource the URL of the resource
   * @return the final name segment of the resource
   */
  public static String baseName(URL resource) {
    String path = resource.getPath();
    return path.substring(path.lastIndexOf('/') + 1);
  }

  /**
   * Find all resources referenced by a method in a bundle. The method's
   * {@link Source} annotation will be examined and the specified locations will
   * be expanded into URLs by which they may be accessed on the local system.
   * 

* This method is sensitive to the locale deferred-binding * property and will attempt to use a best-match lookup by removing locale * components. *

* Loading through a ClassLoader with this method is much slower than the * other findResources methods which make use of the compiler's * ResourceOracle. * * @param logger a TreeLogger that will be used to report errors or warnings * @param context the ResourceContext in which the ResourceGenerator is * operating * @param classLoader the ClassLoader to use when locating resources * @param method the method to examine for {@link Source} annotations * @param defaultSuffixes if the supplied method does not have any * {@link Source} annotations, act as though a Source annotation was * specified, using the name of the method and each of supplied * extensions in the order in which they are specified * @return URLs for each {@link Source} annotation value defined on the * method. * @throws UnableToCompleteException if ore or more of the sources could not * be found. The error will be reported via the logger * provided to this method */ public static URL[] findResources(TreeLogger logger, ClassLoader classLoader, ResourceContext context, JMethod method, String[] defaultSuffixes) throws UnableToCompleteException { return findResources(logger, new Locator[] {new ClassLoaderLocator( classLoader)}, context, method, defaultSuffixes); } /** * Find all resources referenced by a method in a bundle. The method's * {@link Source} annotation will be examined and the specified locations will * be expanded into URLs by which they may be accessed on the local system. *

* This method is sensitive to the locale deferred-binding * property and will attempt to use a best-match lookup by removing locale * components. *

* The compiler's ResourceOracle will be used to resolve resource locations. * If the desired resource cannot be found in the ResourceOracle, this method * will fall back to using the current thread's context ClassLoader. If it is * necessary to alter the way in which resources are located, use the overload * that accepts a ClassLoader. *

* If the method's return type declares the {@link DefaultExtensions} * annotation, the value of this annotation will be used to find matching * resource names if the method lacks a {@link Source} annotation. * * @param logger a TreeLogger that will be used to report errors or warnings * @param context the ResourceContext in which the ResourceGenerator is * operating * @param method the method to examine for {@link Source} annotations * @return URLs for each {@link Source} annotation value defined on the * method. * @throws UnableToCompleteException if ore or more of the sources could not * be found. The error will be reported via the logger * provided to this method */ public static URL[] findResources(TreeLogger logger, ResourceContext context, JMethod method) throws UnableToCompleteException { JClassType returnType = method.getReturnType().isClassOrInterface(); assert returnType != null; DefaultExtensions annotation = returnType.findAnnotationInTypeHierarchy(DefaultExtensions.class); String[] extensions; if (annotation != null) { extensions = annotation.value(); } else { extensions = new String[0]; } return findResources(logger, context, method, extensions); } /** * Find all resources referenced by a method in a bundle. The method's * {@link Source} annotation will be examined and the specified locations will * be expanded into URLs by which they may be accessed on the local system. *

* This method is sensitive to the locale deferred-binding * property and will attempt to use a best-match lookup by removing locale * components. *

* The compiler's ResourceOracle will be used to resolve resource locations. * If the desired resource cannot be found in the ResourceOracle, this method * will fall back to using the current thread's context ClassLoader. If it is * necessary to alter the way in which resources are located, use the overload * that accepts a ClassLoader. * * @param logger a TreeLogger that will be used to report errors or warnings * @param context the ResourceContext in which the ResourceGenerator is * operating * @param method the method to examine for {@link Source} annotations * @param defaultSuffixes if the supplied method does not have any * {@link Source} annotations, act as though a Source annotation was * specified, using the name of the method and each of supplied * extensions in the order in which they are specified * @return URLs for each {@link Source} annotation value defined on the * method. * @throws UnableToCompleteException if ore or more of the sources could not * be found. The error will be reported via the logger * provided to this method */ public static URL[] findResources(TreeLogger logger, ResourceContext context, JMethod method, String[] defaultSuffixes) throws UnableToCompleteException { Locator[] locators = getDefaultLocators(context.getGeneratorContext()); URL[] toReturn = findResources(logger, locators, context, method, defaultSuffixes); return toReturn; } /** * Returns the most recent value of the last-modified header fields of all the Urls * in the resources array. * The result is the number of milliseconds since January 1, 1970 GMT. * * Returns 0 if the last-modified header field of one of the resources cannot * be determined. * * @return the most recent modification date of the resources present in * resources or 0 if not known. */ public static long getLastModified(URL[] resources, TreeLogger logger) { long lastModificationDate = 0; for (URL url : resources) { long lastModified = 0; try { lastModified = url.openConnection().getLastModified(); } catch (IOException e) { // Non-fatal, assuming we can re-open the stream later logger.log(TreeLogger.DEBUG, "Could not determine cached time", e); } if (lastModified == 0) { // either we cannot open the stream either the last modification date is not known return 0; } else { lastModificationDate = Math.max(lastModificationDate, lastModified); } } return lastModificationDate; } /** * Finds a method by following a dotted path interpreted as a series of no-arg * method invocations from an instance of a given root type. * * @param rootType the type from which the search begins * @param pathElements a sequence of no-arg method names * @param expectedReturnType the expected return type of the method to locate, * or null if no constraint on the return type is * necessary * * @return the requested JMethod * @throws NotFoundException if the requested method could not be found */ public static JMethod getMethodByPath(JClassType rootType, List pathElements, JType expectedReturnType) throws NotFoundException { if (pathElements.isEmpty()) { throw new NotFoundException("No path specified"); } JMethod currentMethod = null; JType currentType = rootType; for (String pathElement : pathElements) { JClassType referenceType = currentType.isClassOrInterface(); if (referenceType == null) { throw new NotFoundException("Cannot resolve member " + pathElement + " on type " + currentType.getQualifiedSourceName()); } currentMethod = null; searchType : for (JClassType searchType : referenceType.getFlattenedSupertypeHierarchy()) { for (JMethod method : searchType.getOverloads(pathElement)) { if (method.getParameters().length == 0) { currentMethod = method; break searchType; } } } if (currentMethod == null) { throw new NotFoundException("Could not find no-arg method named " + pathElement + " in type " + currentType.getQualifiedSourceName()); } currentType = currentMethod.getReturnType(); } if (expectedReturnType != null) { JPrimitiveType expectedIsPrimitive = expectedReturnType.isPrimitive(); JClassType expectedIsClassType = expectedReturnType.isClassOrInterface(); boolean error = false; if (expectedIsPrimitive != null) { if (!expectedIsPrimitive.equals(currentMethod.getReturnType())) { error = true; } } else { JClassType returnIsClassType = currentMethod.getReturnType().isClassOrInterface(); if (returnIsClassType == null) { error = true; } else if (!expectedIsClassType.isAssignableFrom(returnIsClassType)) { error = true; } } if (error) { throw new NotFoundException("Expecting return type " + expectedReturnType.getQualifiedSourceName() + " found " + currentMethod.getReturnType().getQualifiedSourceName()); } } return currentMethod; } /** * Try to find a resource with the given resourceName. It will use the default * search order to locate the resource as is used by {@link #findResources}. * * @param logger * @param genContext * @param resourceContext * @param resourceName * @return a URL for the resource, if found */ public static URL tryFindResource(TreeLogger logger, GeneratorContext genContext, ResourceContext resourceContext, String resourceName) { String locale = getLocale(logger, genContext); Locator[] locators = getDefaultLocators(genContext); for (Locator locator : locators) { URL toReturn = tryFindResource(locator, resourceContext, resourceName, locale); if (toReturn != null) { return toReturn; } } return null; } /** * Add the type dependency requirements for a method, to the context. * * @param context * @param method */ private static void addTypeRequirementsForMethod(ResourceContext context, JMethod method) { ClientBundleRequirements reqs = context.getRequirements(); if (reqs != null) { reqs.addTypeHierarchy(method.getEnclosingType()); reqs.addTypeHierarchy((JClassType) method.getReturnType()); } } /** * We want to warn the user about any annotations from ImageBundle or the old * incubator code. */ private static void checkForDeprecatedAnnotations(TreeLogger logger, JMethod method) { for (Class annotationClass : DEPRECATED_ANNOTATION_CLASSES) { if (method.isAnnotationPresent(annotationClass)) { logger.log(TreeLogger.WARN, "Deprecated annotation used; expecting " + Source.class.getCanonicalName() + " but found " + annotationClass.getName() + " instead. It is likely " + "that undesired operation will occur."); } } } /** * Main implementation of findResources. */ private static URL[] findResources(TreeLogger logger, Locator[] locators, ResourceContext context, JMethod method, String[] defaultSuffixes) throws UnableToCompleteException { logger = logger.branch(TreeLogger.DEBUG, "Finding resources"); String locale = getLocale(logger, context.getGeneratorContext()); checkForDeprecatedAnnotations(logger, method); boolean error = false; Source resourceAnnotation = method.getAnnotation(Source.class); URL[] toReturn; if (resourceAnnotation == null) { if (defaultSuffixes != null) { for (String extension : defaultSuffixes) { if (logger.isLoggable(TreeLogger.SPAM)) { logger.log(TreeLogger.SPAM, "Trying default extension " + extension); } for (Locator locator : locators) { URL resourceUrl = tryFindResource(locator, context, getPathRelativeToPackage(method.getEnclosingType().getPackage(), method.getName() + extension), locale); // Take the first match if (resourceUrl != null) { addTypeRequirementsForMethod(context, method); return new URL[] {resourceUrl}; } } } } logger.log(TreeLogger.ERROR, "No " + Source.class.getName() + " annotation and no resources found with default extensions"); toReturn = null; error = true; } else { // The user has put an @Source annotation on the accessor method String[] resources = resourceAnnotation.value(); toReturn = new URL[resources.length]; int tagIndex = 0; for (String resource : resources) { // Try to find the resource relative to the package. URL resourceURL = null; for (Locator locator : locators) { resourceURL = tryFindResource(locator, context, getPathRelativeToPackage(method.getEnclosingType().getPackage(), resource), locale); /* * If we didn't find the resource relative to the package, assume it * is absolute. */ if (resourceURL == null) { resourceURL = tryFindResource(locator, context, resource, locale); } // If we have found a resource, take the first match if (resourceURL != null) { break; } } if (resourceURL == null) { error = true; logger.log(TreeLogger.ERROR, "Resource " + resource + " not found. Is the name specified as ClassLoader.getResource()" + " would expect?"); } toReturn[tagIndex++] = resourceURL; } } if (error) { throw new UnableToCompleteException(); } addTypeRequirementsForMethod(context, method); return toReturn; } /** * Get default list of resource Locators, in the default order. * * @param genContext * @return an ordered array of Locator[] */ private static Locator[] getDefaultLocators(GeneratorContext genContext) { Locator[] locators = { NamedFileLocator.INSTANCE, new ResourceOracleLocator(genContext.getResourcesOracle()), new ClassLoaderLocator(Thread.currentThread().getContextClassLoader())}; return locators; } /** * Get the current locale string. * * @param logger * @param genContext * @return the current locale */ private static String getLocale(TreeLogger logger, GeneratorContext genContext) { String locale; try { PropertyOracle oracle = genContext.getPropertyOracle(); SelectionProperty prop = oracle.getSelectionProperty(logger, "locale"); locale = prop.getCurrentValue(); } catch (BadPropertyValueException e) { locale = null; } return locale; } /** * Converts a package relative path into an absolute path. * * @param pkg the package * @param path a path relative to the package * @return an absolute path */ private static String getPathRelativeToPackage(JPackage pkg, String path) { return pkg.getName().replace('.', '/') + '/' + path; } /** * This performs the locale lookup function for a given resource name. * * @param locator the Locator to use to load the resources * @param resourceName the string name of the desired resource * @param locale the locale of the current rebind permutation * @return a URL by which the resource can be loaded, null if one * cannot be found */ private static URL tryFindResource(Locator locator, String resourceName, String locale) { URL toReturn = null; // Look for locale-specific variants of individual resources if (locale != null) { // Convert language_country_variant to independent pieces String[] localeSegments = locale.split("_"); int lastDot = resourceName.lastIndexOf("."); String prefix = lastDot == -1 ? resourceName : resourceName.substring(0, lastDot); String extension = lastDot == -1 ? "" : resourceName.substring(lastDot); for (int i = localeSegments.length - 1; i >= -1; i--) { String localeInsert = ""; for (int j = 0; j <= i; j++) { localeInsert += "_" + localeSegments[j]; } toReturn = locator.locate(prefix + localeInsert + extension); if (toReturn != null) { break; } } } else { toReturn = locator.locate(resourceName); } return toReturn; } /** * Performs the locale lookup function for a given resource name. Will also * add the located resource to the requirements object for the context. * * @param locator the Locator to use to load the resources * @param context the ResourceContext * @param resourceName the string name of the desired resource * @param locale the locale of the current rebind permutation * @return a URL by which the resource can be loaded, null if one * cannot be found */ private static URL tryFindResource(Locator locator, ResourceContext context, String resourceName, String locale) { URL toReturn = tryFindResource(locator, resourceName, locale); if (context != null) { ClientBundleRequirements reqs = context.getRequirements(); if (reqs != null) { reqs.addResolvedResource(resourceName, toReturn); } } return toReturn; } /** * Utility class. */ private ResourceGeneratorUtil() { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy