Package com.google.gwt.resources.ext

Source Code of com.google.gwt.resources.ext.ResourceGeneratorUtil$Locator

/*
* 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.impl.ResourceGeneratorUtilImpl;
import com.google.gwt.core.ext.impl.ResourceLocatorImpl;
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.ResourceOracle;
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;

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

  /**
   * A locator that uses ResourceLocatorImpl to locate resources either in the ResourceOracle or the
   * ClassLoader.
   */
  private static class GeneralLocator implements Locator {
    private final ResourceOracle resourceOracle;
    private final TreeLogger logger;

    public GeneralLocator(TreeLogger logger, ResourceOracle resourceOracle) {
      this.logger = logger;
      this.resourceOracle = resourceOracle;
    }

    @Override
    public URL locate(String resourceName) {
      return ResourceLocatorImpl.tryFindResourceUrl(logger, resourceOracle, 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() {
    }

    @Override
    public URL locate(String resourceName) {
      File f = ResourceGeneratorUtilImpl.getGeneratedFile(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);
  }

  /**
   * 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<Class<? extends Annotation>> DEPRECATED_ANNOTATION_CLASSES;

  static {
    List<Class<? extends Annotation>> classes = new ArrayList<Class<? extends Annotation>>(
        DEPRECATED_ANNOTATION_NAMES.length);

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

        // Possibly throws ClassCastException
        Class<? extends Annotation> 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 <code>file</code>
   *          should be made available
   * @param file the File whose contents are to be provided to the ClientBundle
   *          system
   */
  // TODO(stalcup): the addNamedFile() and Locator system are redundant and need to be deleted. They
  // exist because resources generated via the GeneratorContext are not queryable via the available
  // ResourceOracle. This should be corrected.
  public static void addNamedFile(String resourceName, File file) {
    // User code should not refer to private Compiler classes, but it is being done here in lieu of
    // the above mentioned refactoring.
    ResourceGeneratorUtilImpl.addGeneratedFile(resourceName, file);
  }

  /**
   * Returns the base filename of a resource. The behavior is similar to the unix
   * command <code>basename</code>.
   *
   * @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.
   * <p>
   * This method is sensitive to the <code>locale</code> deferred-binding
   * property and will attempt to use a best-match lookup by removing locale
   * components.
   * <p>
   * 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.
   * <p>
   * 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 <code>logger</code>
   *           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.
   * <p>
   * This method is sensitive to the <code>locale</code> deferred-binding
   * property and will attempt to use a best-match lookup by removing locale
   * components.
   * <p>
   * 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 <code>logger</code>
   *           provided to this method
   */
  public static URL[] findResources(TreeLogger logger, ResourceContext context,
      JMethod method, String[] defaultSuffixes)
      throws UnableToCompleteException {
    Locator[] locators = getDefaultLocators(logger, context.getGeneratorContext());
    URL[] toReturn = findResources(logger, locators, context, method,
        defaultSuffixes);
    return toReturn;
  }

  /**
   * Returns the most recent value of the <code>last-modified</code> header fields of all the Urls
   * in the <code>resources</code> array.
   * The result is the number of milliseconds since January 1, 1970 GMT.
   *
   * Returns 0 if the <code>last-modified</code> header field of one of the resources cannot
   * be determined.
   *
   * @return  the most recent modification date of the resources present in
   *          <code>resources</code> 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 <code>null</code> 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<String> 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(logger, 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<? extends Annotation> 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.
   *
   * @return an ordered array of Locator[]
   */
  private static Locator[] getDefaultLocators(TreeLogger logger, GeneratorContext genContext) {
    return new Locator[] {
        NamedFileLocator.INSTANCE, new GeneralLocator(logger, genContext.getResourcesOracle())};
  }

  /**
   * 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, <code>null</code> 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, <code>null</code> 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() {
  }
}
TOP

Related Classes of com.google.gwt.resources.ext.ResourceGeneratorUtil$Locator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.