Add error for when method return/parameter type has type parameter that is hidden

The error is hidden by default, it can be enabled by using
the flag -error 121

BUG: 19091604
Change-Id: I28805fa167a599a2ff6baef5d0853fdf863639c2
diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java
index c0ba831..84df16b 100644
--- a/src/com/google/doclava/Errors.java
+++ b/src/com/google/doclava/Errors.java
@@ -171,6 +171,7 @@
   public static final Error BROKEN_SINCE_FILE = new Error(118, ERROR);
   public static final Error INVALID_CONTENT_TYPE = new Error(119, ERROR);
   public static final Error INVALID_SAMPLE_INDEX = new Error(120, ERROR);
+  public static final Error HIDDEN_TYPE_PARAMETER = new Error(121, HIDDEN);
 
   public static final Error[] ERRORS =
       {UNRESOLVED_LINK, BAD_INCLUDE_TAG, UNKNOWN_TAG, UNKNOWN_PARAM_TAG_NAME,
@@ -182,7 +183,7 @@
           CHANGED_TRANSIENT, CHANGED_VOLATILE, CHANGED_TYPE, CHANGED_VALUE, CHANGED_SUPERCLASS,
           CHANGED_SCOPE, CHANGED_ABSTRACT, CHANGED_THROWS, CHANGED_NATIVE, CHANGED_CLASS,
           CHANGED_DEPRECATED, CHANGED_SYNCHRONIZED, ADDED_FINAL_UNINSTANTIABLE, REMOVED_FINAL,
-          BROKEN_SINCE_FILE, INVALID_CONTENT_TYPE};
+          BROKEN_SINCE_FILE, INVALID_CONTENT_TYPE, HIDDEN_TYPE_PARAMETER};
 
   public static boolean setErrorLevel(int code, int level) {
     for (Error e : ERRORS) {
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index 2b31321..f387801 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -94,18 +94,35 @@
                 + m.name() + " is deprecated");
           }
 
-          ClassInfo returnClass = m.returnType().asClassInfo();
-          if (returnClass != null && returnClass.isHiddenOrRemoved()) {
-            Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Method " + cl.qualifiedName()
-                + "." + m.name() + " returns unavailable type " + returnClass.name());
+          ClassInfo hiddenReturnClass = findHiddenClasses(m.returnType());
+          if (null != hiddenReturnClass) {
+            if (hiddenReturnClass.qualifiedName() == m.returnType().asClassInfo().qualifiedName()) {
+              // Return type is hidden
+              Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Method " + cl.qualifiedName()
+                  + "." + m.name() + " returns unavailable type " + hiddenReturnClass.name());
+            } else {
+              // Return type contains a generic parameter
+              Errors.error(Errors.HIDDEN_TYPE_PARAMETER, m.position(), "Method " + cl.qualifiedName()
+                  + "." + m.name() + " returns unavailable type " + hiddenReturnClass.name()
+                  + " as a type parameter");
+            }
           }
 
           for (ParameterInfo p :  m.parameters()) {
             TypeInfo t = p.type();
             if (!t.isPrimitive()) {
-              if (t.asClassInfo().isHiddenOrRemoved()) {
-                Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(), "Parameter of unavailable type "
-                    + t.fullName() + " in " + cl.qualifiedName() + "." + m.name() + "()");
+              if (null != findHiddenClasses(t)) {
+                if (hiddenReturnClass.qualifiedName() == t.asClassInfo().qualifiedName()) {
+                  // Parameter type is hidden
+                  Errors.error(Errors.UNAVAILABLE_SYMBOL, m.position(),
+                      "Parameter of unavailable type " + t.fullName() + " in " + cl.qualifiedName()
+                      + "." + m.name() + "()");
+                } else {
+                  // Parameter type contains a generic parameter
+                  Errors.error(Errors.HIDDEN_TYPE_PARAMETER, m.position(),
+                      "Parameter uses type parameter of unavailable type " + t.fullName() + " in "
+                      + cl.qualifiedName() + "." + m.name() + "()");
+                }
               }
             }
           }
@@ -193,6 +210,22 @@
     }
   }
 
+  private static ClassInfo findHiddenClasses(TypeInfo ti) {
+    ClassInfo ci = ti.asClassInfo();
+    if (ci == null) return null;
+    if (ci.isHiddenOrRemoved()) return ci;
+    if (ti.typeArguments() != null) {
+      for (TypeInfo tii : ti.typeArguments()) {
+        // Avoid infinite recursion in the case of Foo<T extends Foo>
+        if (tii.qualifiedTypeName() != ti.qualifiedTypeName()) {
+          ClassInfo hiddenClass = findHiddenClasses(tii);
+          if (hiddenClass != null) return hiddenClass;
+        }
+      }
+    }
+    return null;
+  }
+
   public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) {
 
     if (!notStrippable.add(cl)) {