Formalize how default extensions are provided by ClientBundle resource types.

Patch by: bobv
Review by: rjrjr

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5978 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
index d3ab066..c8b112e 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
@@ -19,6 +19,9 @@
 import com.google.gwt.dev.util.collect.Sets;
 
 import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -45,8 +48,8 @@
   }
 
   /**
-   * Returns <code>true</code> if the rhs array type can be assigned to the
-   * lhs array type.
+   * Returns <code>true</code> if the rhs array type can be assigned to the lhs
+   * array type.
    */
   private static boolean areArraysAssignable(JArrayType lhsType,
       JArrayType rhsType) {
@@ -327,8 +330,8 @@
   }
 
   /**
-   * Cached set of supertypes for this type (including itself).  If null,
-   * the set has not been calculated yet.
+   * Cached set of supertypes for this type (including itself). If null, the set
+   * has not been calculated yet.
    */
   private Set<JClassType> flattenedSupertypes;
 
@@ -358,6 +361,65 @@
     return null;
   }
 
+  /**
+   * Find an annotation on a type or on one of its superclasses or
+   * superinterfaces.
+   * <p>
+   * This provides semantics similar to that of
+   * {@link java.lang.annotation.Inherited} except that it checks all types to
+   * which this type is assignable. {@code @Inherited} only works on
+   * superclasses, not superinterfaces.
+   * <p>
+   * Annotations present on the superclass chain will be returned preferentially
+   * over those found in the superinterface hierarchy. Note that the annotation
+   * does not need to be tagged with {@code @Inherited} in order to be returned
+   * from the superclass chain.
+   * 
+   * @param annotationType the type of the annotation to look for
+   * @return the desired annotation or <code>null</code> if the annotation is
+   *         not present in the type's type hierarchy
+   */
+  public <T extends Annotation> T findAnnotationInTypeHierarchy(
+      Class<T> annotationType) {
+
+    // Remember what we've seen to avoid loops
+    Set<JClassType> seen = new HashSet<JClassType>();
+
+    // Work queue
+    List<JClassType> searchTypes = new LinkedList<JClassType>();
+    searchTypes.add(this);
+
+    T toReturn = null;
+
+    while (!searchTypes.isEmpty()) {
+      JClassType current = searchTypes.remove(0);
+
+      if (!seen.add(current)) {
+        continue;
+      }
+
+      toReturn = current.getAnnotation(annotationType);
+      if (toReturn != null) {
+        /*
+         * First one wins. It might be desirable at some point to have a
+         * variation that can return more than one instance of the annotation if
+         * it is present on multiple supertypes.
+         */
+        break;
+      }
+
+      if (current.getSuperclass() != null) {
+        // Add the superclass at the front of the list
+        searchTypes.add(0, current.getSuperclass());
+      }
+
+      // Superinterfaces
+      Collections.addAll(searchTypes, current.getImplementedInterfaces());
+    }
+
+    return toReturn;
+  }
+
   public abstract JConstructor findConstructor(JType[] paramTypes);
 
   public abstract JField findField(String name);
@@ -446,12 +508,12 @@
       Class<? extends Annotation> annotationClass);
 
   /**
-   * Returns <code>true</code> if this {@link JClassType} is assignable from
-   * the specified {@link JClassType} parameter.
+   * Returns <code>true</code> if this {@link JClassType} is assignable from the
+   * specified {@link JClassType} parameter.
    * 
    * @param possibleSubtype possible subtype of this {@link JClassType}
-   * @return <code>true</code> if this {@link JClassType} is assignable from
-   *         the specified {@link JClassType} parameter
+   * @return <code>true</code> if this {@link JClassType} is assignable from the
+   *         specified {@link JClassType} parameter
    * 
    * @throws NullPointerException if <code>possibleSubtype</code> is
    *           <code>null</code>
@@ -487,7 +549,7 @@
    * Determines if the class can be constructed using a simple <code>new</code>
    * operation. Specifically, the class must
    * <ul>
-   * <li>be a class rather than an interface, </li>
+   * <li>be a class rather than an interface,</li>
    * <li>have either no constructors or a parameterless constructor, and</li>
    * <li>be a top-level class or a static nested class.</li>
    * </ul>
diff --git a/user/src/com/google/gwt/resources/client/CssResource.java b/user/src/com/google/gwt/resources/client/CssResource.java
index dc3a446..908c777 100644
--- a/user/src/com/google/gwt/resources/client/CssResource.java
+++ b/user/src/com/google/gwt/resources/client/CssResource.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.resources.client;
 
+import com.google.gwt.resources.ext.DefaultExtensions;
 import com.google.gwt.resources.ext.ResourceGeneratorType;
 import com.google.gwt.resources.rg.CssResourceGenerator;
 
@@ -32,9 +33,9 @@
  * <li>{@code String someClassName();} will allow the css class
  * <code>.someClassName</code> to be obfuscated at runtime. The function will
  * return the obfuscated class name.</li>
- * <li>{@code <primitive numeric type or String> someDefName();} will allow 
- * access to the values defined by {@literal @def} rules within the CSS file. 
- * The defined value must be a raw number, a CSS length, or a percentage value 
+ * <li>{@code <primitive numeric type or String> someDefName();} will allow
+ * access to the values defined by {@literal @def} rules within the CSS file.
+ * The defined value must be a raw number, a CSS length, or a percentage value
  * if it is to be returned as a numeric type.
  * </ul>
  * 
@@ -94,6 +95,7 @@
  * @see <a href="http://code.google.com/p/google-web-toolkit/wiki/CssResource"
  *      >CssResource design doc</a>
  */
+@DefaultExtensions(value = {".css"})
 @ResourceGeneratorType(CssResourceGenerator.class)
 public interface CssResource extends ResourcePrototype {
   /**
diff --git a/user/src/com/google/gwt/resources/client/ExternalTextResource.java b/user/src/com/google/gwt/resources/client/ExternalTextResource.java
index 0e5d4cb..a1b0ba7 100644
--- a/user/src/com/google/gwt/resources/client/ExternalTextResource.java
+++ b/user/src/com/google/gwt/resources/client/ExternalTextResource.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.resources.client;
 
+import com.google.gwt.resources.ext.DefaultExtensions;
 import com.google.gwt.resources.ext.ResourceGeneratorType;
 import com.google.gwt.resources.rg.ExternalTextResourceGenerator;
 
@@ -23,6 +24,7 @@
  * not inlined into the compiled output. This is suitable for resources that are
  * not required as part of program initialization.
  */
+@DefaultExtensions(value = {".txt"})
 @ResourceGeneratorType(ExternalTextResourceGenerator.class)
 public interface ExternalTextResource extends ResourcePrototype {
   void getText(ResourceCallback<TextResource> callback)
diff --git a/user/src/com/google/gwt/resources/client/ImageResource.java b/user/src/com/google/gwt/resources/client/ImageResource.java
index 4a30dd7..8095ed5 100644
--- a/user/src/com/google/gwt/resources/client/ImageResource.java
+++ b/user/src/com/google/gwt/resources/client/ImageResource.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.resources.client;
 
+import com.google.gwt.resources.ext.DefaultExtensions;
 import com.google.gwt.resources.ext.ResourceGeneratorType;
 import com.google.gwt.resources.rg.ImageResourceGenerator;
 
@@ -25,6 +26,7 @@
 /**
  * Provides access to image resources at runtime.
  */
+@DefaultExtensions(value = {".png", ".jpg", ".gif", ".bmp"})
 @ResourceGeneratorType(ImageResourceGenerator.class)
 public interface ImageResource extends ResourcePrototype {
 
diff --git a/user/src/com/google/gwt/resources/client/TextResource.java b/user/src/com/google/gwt/resources/client/TextResource.java
index 69ee17a..b1f41ed 100644
--- a/user/src/com/google/gwt/resources/client/TextResource.java
+++ b/user/src/com/google/gwt/resources/client/TextResource.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.resources.client;
 
+import com.google.gwt.resources.ext.DefaultExtensions;
 import com.google.gwt.resources.ext.ResourceGeneratorType;
 import com.google.gwt.resources.rg.TextResourceGenerator;
 
@@ -22,6 +23,7 @@
  * A resource that contains text that should be incorporated into the compiled
  * output.
  */
+@DefaultExtensions(value = {".txt"})
 @ResourceGeneratorType(TextResourceGenerator.class)
 public interface TextResource extends ResourcePrototype {
   String getText();
diff --git a/user/src/com/google/gwt/resources/ext/DefaultExtensions.java b/user/src/com/google/gwt/resources/ext/DefaultExtensions.java
new file mode 100644
index 0000000..8c2047b
--- /dev/null
+++ b/user/src/com/google/gwt/resources/ext/DefaultExtensions.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies the default extensions for a resource type. This annotation is
+ * intended to allow external tooling to know which filename extensions the
+ * ClientBundle system will search for if no
+ * {@link com.google.gwt.resources.client.ClientBundle.Source} annotation is
+ * present on an accessor method.
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DefaultExtensions {
+  String[] value();
+}
diff --git a/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java b/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
index 4f09b76..0c92052 100644
--- a/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
+++ b/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
@@ -20,6 +20,7 @@
 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.dev.resource.Resource;
@@ -171,6 +172,10 @@
    * 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 an {@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
@@ -184,7 +189,16 @@
    */
   public static URL[] findResources(TreeLogger logger, ResourceContext context,
       JMethod method) throws UnableToCompleteException {
-    return findResources(logger, context, method, new String[0]);
+    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);
   }
 
   /**
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
index d48a4ea..a18597e 100644
--- a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
+++ b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
@@ -418,40 +418,18 @@
   private Class<? extends ResourceGenerator> findResourceGenerator(
       TreeLogger logger, TypeOracle typeOracle, JMethod method)
       throws UnableToCompleteException {
-
     JClassType resourceType = method.getReturnType().isClassOrInterface();
     assert resourceType != null;
 
-    List<JClassType> searchTypes = new ArrayList<JClassType>();
-    searchTypes.add(resourceType);
-
-    ResourceGeneratorType generatorType = null;
-
-    while (!searchTypes.isEmpty()) {
-      JClassType current = searchTypes.remove(0);
-      generatorType = current.getAnnotation(ResourceGeneratorType.class);
-      if (generatorType != null) {
-        break;
-      }
-
-      if (current.getSuperclass() != null) {
-        searchTypes.add(current.getSuperclass());
-      }
-
-      for (JClassType t : current.getImplementedInterfaces()) {
-        searchTypes.add(t);
-      }
-    }
-
-    if (generatorType == null) {
+    ResourceGeneratorType annotation = resourceType.findAnnotationInTypeHierarchy(ResourceGeneratorType.class);
+    if (annotation == null) {
       logger.log(TreeLogger.ERROR, "No @"
-          + ResourceGeneratorType.class.getName()
-          + " was specifed for resource type "
+          + ResourceGeneratorType.class.getName() + " was specifed for type "
           + resourceType.getQualifiedSourceName() + " or its supertypes");
       throw new UnableToCompleteException();
     }
 
-    return generatorType.value();
+    return annotation.value();
   }
 
   /**
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index e8af11d..bb96a2b 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -1002,8 +1002,6 @@
    */
   private static final int CONCAT_EXPRESSION_LIMIT = 20;
 
-  private static final String[] DEFAULT_EXTENSIONS = new String[] {".css"};
-
   /**
    * These constants are used to cache obfuscated class names.
    */
@@ -1377,7 +1375,7 @@
     }
 
     URL[] resources = ResourceGeneratorUtil.findResources(logger, context,
-        method, DEFAULT_EXTENSIONS);
+        method);
 
     if (resources.length == 0) {
       logger.log(TreeLogger.ERROR, "At least one source must be specified");
diff --git a/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
index c5f3d31..ee62178 100644
--- a/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
@@ -41,7 +41,6 @@
  */
 public final class ExternalTextResourceGenerator extends
     AbstractResourceGenerator {
-  private static final String[] DEFAULT_EXTENSIONS = new String[] {".txt"};
   private StringBuffer data;
   private boolean first;
   private String urlExpression;
@@ -112,8 +111,7 @@
       ClientBundleRequirements requirements, JMethod method)
       throws UnableToCompleteException {
 
-    URL[] urls = ResourceGeneratorUtil.findResources(logger, context, method,
-        DEFAULT_EXTENSIONS);
+    URL[] urls = ResourceGeneratorUtil.findResources(logger, context, method);
 
     if (urls.length != 1) {
       logger.log(TreeLogger.ERROR, "Exactly one resource must be specified",
diff --git a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
index b0df671..720116e 100644
--- a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
@@ -45,8 +45,6 @@
  * Builds an image strip for all ImageResources defined within an ClientBundle.
  */
 public final class ImageResourceGenerator extends AbstractResourceGenerator {
-  private static final String[] DEFAULT_EXTENSIONS = new String[] {
-      ".png", ".jpg", ".gif", ".bmp"};
   private Map<String, ImageRect> imageRectsByName;
   private Map<ImageRect, ImageBundleBuilder> buildersByImageRect;
   private Map<RepeatStyle, ImageBundleBuilder> buildersByRepeatStyle;
@@ -199,7 +197,7 @@
       ClientBundleRequirements requirements, JMethod method)
       throws UnableToCompleteException {
     URL[] resources = ResourceGeneratorUtil.findResources(logger, context,
-        method, DEFAULT_EXTENSIONS);
+        method);
 
     if (resources.length != 1) {
       logger.log(TreeLogger.ERROR, "Exactly one image may be specified", null);
diff --git a/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java b/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
index 88b3d33..10a8820 100644
--- a/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
@@ -33,13 +33,11 @@
  */
 public final class TextResourceGenerator extends AbstractResourceGenerator {
 
-  private static final String[] DEFAULT_EXTENSIONS = new String[] {".txt"};
-
   @Override
   public String createAssignment(TreeLogger logger, ResourceContext context,
       JMethod method) throws UnableToCompleteException {
     URL[] resources = ResourceGeneratorUtil.findResources(logger, context,
-        method, DEFAULT_EXTENSIONS);
+        method);
 
     if (resources.length != 1) {
       logger.log(TreeLogger.ERROR, "Exactly one resource must be specified",