Merge tag android-5.1.0_r1 into AOSP_5.1_MERGE

Change-Id: I3a56e00ba95d0d394fb239f8385e01715222087e
diff --git a/res/assets/templates/macros.cs b/res/assets/templates/macros.cs
index 678710a..317b36e 100644
--- a/res/assets/templates/macros.cs
+++ b/res/assets/templates/macros.cs
@@ -44,6 +44,23 @@
   /if ?><?cs
 /def ?>
 
+<?cs
+def:simple_type_link(type)?><?cs
+  if:type.link?><?cs
+    if:type.federated ?><a href="<?cs var:type.link ?>"><?cs var:type.label ?></a><?cs
+    else ?><a href="<?cs var:toroot ?><?cs var:type.link ?>"><?cs var:type.label ?></a><?cs
+    /if?><?cs
+  else ?><?cs var:type.label ?><?cs
+  /if?><?cs
+  if:subcount(type.typeArguments)?>&lt;<?cs
+    each:t=type.typeArguments?><?cs
+      call:type_link_impl(t, "true")?><?cs
+      if:!last(t) ?>,&nbsp;<?cs
+      /if ?><?cs
+    /each ?>&gt;<?cs
+  /if ?><?cs
+/def ?>
+
 <?cs def:class_name(type) ?><?cs call:type_link_impl(type, "false") ?><?cs /def ?>
 <?cs def:type_link2(type,nav) ?><?cs call:type_link_impl2(type, "true", nav) ?><?cs /def ?>
 <?cs def:type_link(type) ?><?cs call:type_link2(type, "false") ?><?cs /def ?>
@@ -59,7 +76,7 @@
 <?cs # A comma separated parameter list ?><?cs 
 def:parameter_list(params) ?><?cs
   each:param = params ?><?cs
-      call:type_link(param.type)?> <?cs
+      call:simple_type_link(param.type)?> <?cs
       var:param.name ?><?cs
       if: name(param)!=subcount(params)-1?>, <?cs /if ?><?cs
   /each ?><?cs
@@ -74,6 +91,10 @@
         if:!tag.federatedSite ?><?cs
           var:toroot ?><?cs
         /if ?><?cs var:tag.href ?>"><?cs var:tag.label ?></a></code><?cs
+      elif:tag.kind == "@linkplain" ?><a href="<?cs
+        if:!tag.federatedSite ?><?cs
+          var:toroot ?><?cs
+        /if ?><?cs var:tag.href ?>"><?cs var:tag.label ?></a></a><?cs
       elif:tag.kind == "@seeHref" ?><a href="<?cs var:tag.href ?>"><?cs var:tag.label ?></a><?cs
       elif:tag.kind == "@seeJustLabel" ?><?cs var:tag.label ?><?cs
       elif:tag.kind == "@value" ?><code><a href="<?cs
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index add8270..f5309bb 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -19,20 +19,50 @@
 import com.google.clearsilver.jsilver.data.Data;
 import com.sun.javadoc.ClassDoc;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.Vector;
 
 public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Scoped, Resolvable {
+
+  /**
+   * Contains a ClassInfo and a TypeInfo.
+   * <p>
+   * This is used to match a ClassInfo, which doesn't keep track of its type parameters
+   * and a type which does.
+   */
+  private class ClassTypePair {
+    private final ClassInfo mClassInfo;
+    private final TypeInfo mTypeInfo;
+
+    public ClassTypePair(ClassInfo cl, TypeInfo t) {
+      mClassInfo = cl;
+      mTypeInfo = t;
+    }
+
+    public ClassInfo classInfo() {
+      return mClassInfo;
+    }
+
+    public TypeInfo typeInfo() {
+      return mTypeInfo;
+    }
+
+    public Map<String, TypeInfo> getTypeArgumentMapping() {
+      return TypeInfo.getTypeArgumentMapping(classInfo(), typeInfo());
+    }
+  }
+
   public static final Comparator<ClassInfo> comparator = new Comparator<ClassInfo>() {
     public int compare(ClassInfo a, ClassInfo b) {
       return a.name().compareTo(b.name());
@@ -148,6 +178,9 @@
     mSelfFields = null;
     mSelfAttributes = null;
     mDeprecatedKnown = false;
+    mSuperclassesWithTypes = null;
+    mInterfacesWithTypes = null;
+    mAllInterfacesWithTypes = null;
 
     Collections.sort(mEnumConstants, FieldInfo.comparator);
     Collections.sort(mInnerClasses, ClassInfo.comparator);
@@ -279,6 +312,130 @@
     return result;
   }
 
+  /**
+   * List of only direct interface's classes, without worrying about type param mapping.
+   * This can't be lazy loaded, because its overloads depend on changing type parameters
+   * passed in from the callers.
+   */
+  private List<ClassTypePair> justMyInterfacesWithTypes() {
+    return justMyInterfacesWithTypes(Collections.<String, TypeInfo>emptyMap());
+  }
+
+  /**
+   * List of only direct interface's classes and their parameterized types.
+   * This can't be lazy loaded, because of the passed in typeArgumentsMap.
+   */
+  private List<ClassTypePair> justMyInterfacesWithTypes(Map<String, TypeInfo> typeArgumentsMap) {
+    if (mRealInterfaces == null || mRealInterfaceTypes == null) {
+      return Collections.<ClassTypePair>emptyList();
+    }
+
+    List<ClassTypePair> list = new ArrayList<ClassTypePair>();
+    for (int i = 0; i < mRealInterfaces.size(); i++) {
+      ClassInfo iface = mRealInterfaces.get(i);
+      TypeInfo type = mRealInterfaceTypes.get(i);
+      if (iface != null && type != null) {
+        type = type.getTypeWithArguments(typeArgumentsMap);
+        if (iface.checkLevel()) {
+          list.add(new ClassTypePair(iface, type));
+        } else {
+          // add the interface's interfaces
+          Map<String, TypeInfo> map = TypeInfo.getTypeArgumentMapping(iface.asTypeInfo(), type);
+          list.addAll(iface.justMyInterfacesWithTypes(map));
+        }
+      }
+    }
+    return list;
+  }
+
+  /**
+   * List of only direct interface's classes, and any hidden superclass's direct interfaces
+   * between this class and the first visible superclass and those interface class's parameterized types.
+   */
+  private ArrayList<ClassTypePair> interfacesWithTypes() {
+    if (mInterfacesWithTypes == null) {
+      mInterfacesWithTypes = new ArrayList<ClassTypePair>();
+
+      Iterator<ClassTypePair> itr = superClassesWithTypes().iterator();
+      // skip the first one, which is this class
+      itr.next();
+      while (itr.hasNext()) {
+        ClassTypePair ctp = itr.next();
+        if (ctp.classInfo().checkLevel()) {
+          break;
+        } else {
+          // fill mInterfacesWithTypes from the hidden superclass
+          mInterfacesWithTypes.addAll(
+              ctp.classInfo().justMyInterfacesWithTypes(ctp.getTypeArgumentMapping()));
+        }
+      }
+      mInterfacesWithTypes.addAll(
+          justMyInterfacesWithTypes());
+    }
+    return mInterfacesWithTypes;
+  }
+
+  /**
+   * List of all interface's classes reachable in this class's inheritance hierarchy
+   * and those interface class's parameterized types.
+   */
+  private ArrayList<ClassTypePair> allInterfacesWithTypes() {
+    if (mAllInterfacesWithTypes == null) {
+        mAllInterfacesWithTypes = new ArrayList<ClassTypePair>();
+        Queue<ClassTypePair> toParse = new ArrayDeque<ClassTypePair>();
+        Set<String> visited = new HashSet<String>();
+
+        Iterator<ClassTypePair> itr = superClassesWithTypes().iterator();
+        // skip the first one, which is this class
+        itr.next();
+        while (itr.hasNext()) {
+          ClassTypePair ctp = itr.next();
+          toParse.addAll(
+              ctp.classInfo().justMyInterfacesWithTypes(ctp.getTypeArgumentMapping()));
+        }
+        toParse.addAll(justMyInterfacesWithTypes());
+        while (!toParse.isEmpty()) {
+          ClassTypePair ctp = toParse.remove();
+          if (!visited.contains(ctp.typeInfo().fullName())) {
+            mAllInterfacesWithTypes.add(ctp);
+            visited.add(ctp.typeInfo().fullName());
+            toParse.addAll(ctp.classInfo().justMyInterfacesWithTypes(ctp.getTypeArgumentMapping()));
+          }
+        }
+    }
+    return mAllInterfacesWithTypes;
+  }
+
+  /**
+   * A list of ClassTypePairs that contain all superclasses
+   * and their corresponding types. The types will have type parameters
+   * cascaded upwards so they match, if any classes along the way set them.
+   * The list includes the current class, and is an ascending order up the
+   * heirarchy tree.
+   * */
+  private ArrayList<ClassTypePair> superClassesWithTypes() {
+    if (mSuperclassesWithTypes == null) {
+      mSuperclassesWithTypes = new ArrayList<ClassTypePair>();
+
+      ClassTypePair lastCtp = new ClassTypePair(this, this.asTypeInfo());
+      mSuperclassesWithTypes.add(lastCtp);
+
+      Map<String, TypeInfo> typeArgumentsMap;
+      ClassInfo superclass = mRealSuperclass;
+      TypeInfo supertype = mRealSuperclassType;
+      TypeInfo nextType;
+      while (superclass != null && supertype != null) {
+        typeArgumentsMap = lastCtp.getTypeArgumentMapping();
+        lastCtp = new ClassTypePair(superclass, supertype.getTypeWithArguments(typeArgumentsMap));
+        mSuperclassesWithTypes.add(lastCtp);
+
+        supertype = superclass.mRealSuperclassType;
+        superclass = superclass.mRealSuperclass;
+      }
+    }
+    return mSuperclassesWithTypes;
+  }
+
   private static void gatherHiddenInterfaces(ClassInfo cl, HashSet<ClassInfo> interfaces) {
     for (ClassInfo iface : cl.mRealInterfaces) {
       if (iface.checkLevel()) {
@@ -526,10 +683,10 @@
     return mAllSelfFields;
   }
 
-  private void gatherMethods(ClassInfo owner, ClassInfo cl, HashMap<String, MethodInfo> methods) {
-    for (MethodInfo m : cl.selfMethods()) {
+  private void gatherMethods(ClassInfo owner, ClassTypePair ctp, HashMap<String, MethodInfo> methods) {
+    for (MethodInfo m : ctp.classInfo().selfMethods()) {
       if (m.checkLevel()) {
-        methods.put(m.name() + m.signature(), m.cloneForClass(owner));
+        methods.put(m.name() + m.signature(), m.cloneForClass(owner, ctp.getTypeArgumentMapping()));
       }
     }
   }
@@ -538,12 +695,18 @@
     if (mSelfMethods == null) {
         HashMap<String, MethodInfo> methods = new HashMap<String, MethodInfo>();
       // our hidden parents
-      if (mRealSuperclass != null && !mRealSuperclass.checkLevel()) {
-        gatherMethods(this, mRealSuperclass, methods);
+      for (ClassTypePair ctp : superClassesWithTypes()) {
+        // this class is included in this list, so skip it!
+        if (ctp.classInfo() != this) {
+          if (ctp.classInfo().checkLevel()) {
+            break;
+          }
+          gatherMethods(this, ctp, methods);
+        }
       }
-      for (ClassInfo iface : mRealInterfaces) {
-        if (!iface.checkLevel()) {
-          gatherMethods(this, iface, methods);
+      for (ClassTypePair ctp : justMyInterfacesWithTypes(Collections.<String, TypeInfo>emptyMap())) {
+        if (!ctp.classInfo().checkLevel()) {
+          gatherMethods(this, ctp, methods);
         }
       }
       // mine
@@ -1093,23 +1256,19 @@
     containingPackage().makeClassLinkListHDF(data, "class.package");
 
     // inheritance hierarchy
-    Vector<ClassInfo> superClasses = new Vector<ClassInfo>();
-    superClasses.add(this);
-    ClassInfo supr = superclass();
-    while (supr != null) {
-      superClasses.add(supr);
-      supr = supr.superclass();
-    }
-    n = superClasses.size();
-    for (i = 0; i < n; i++) {
-      supr = superClasses.elementAt(n - i - 1);
-
-      supr.asTypeInfo().makeQualifiedHDF(data, "class.inheritance." + i + ".class");
-      supr.asTypeInfo().makeHDF(data, "class.inheritance." + i + ".short_class");
-      j = 0;
-      for (TypeInfo t : supr.interfaceTypes()) {
-        t.makeHDF(data, "class.inheritance." + i + ".interfaces." + j);
-        j++;
+    List<ClassTypePair> ctplist = superClassesWithTypes();
+    n = ctplist.size();
+    for (i = 0; i < ctplist.size(); i++) {
+      // go in reverse order
+      ClassTypePair ctp = ctplist.get(n - i - 1);
+      if (ctp.classInfo().checkLevel()) {
+        ctp.typeInfo().makeQualifiedHDF(data, "class.inheritance." + i + ".class");
+        ctp.typeInfo().makeHDF(data, "class.inheritance." + i + ".short_class");
+        j = 0;
+        for (ClassTypePair t : ctp.classInfo().interfacesWithTypes()) {
+          t.typeInfo().makeHDF(data, "class.inheritance." + i + ".interfaces." + j);
+          j++;
+        }
       }
     }
 
@@ -1281,70 +1440,68 @@
     }
 
     // inherited methods
-    Set<ClassInfo> interfaces = new TreeSet<ClassInfo>();
-    addInterfaces(interfaces(), interfaces);
-    ClassInfo cl = superclass();
+    Iterator<ClassTypePair> superclassesItr = superClassesWithTypes().iterator();
+    superclassesItr.next(); // skip the first one, which is the current class
+    ClassTypePair superCtp;
     i = 0;
-    while (cl != null) {
-      addInterfaces(cl.interfaces(), interfaces);
-      makeInheritedHDF(data, i, cl);
-      cl = cl.superclass();
-      i++;
+    while (superclassesItr.hasNext()) {
+      superCtp = superclassesItr.next();
+      if (superCtp.classInfo().checkLevel()) {
+        makeInheritedHDF(data, i, superCtp);
+        i++;
+      }
     }
-    for (ClassInfo iface : interfaces) {
-      makeInheritedHDF(data, i, iface);
-      i++;
+    Iterator<ClassTypePair> interfacesItr = allInterfacesWithTypes().iterator();
+    while (interfacesItr.hasNext()) {
+      superCtp = interfacesItr.next();
+      if (superCtp.classInfo().checkLevel()) {
+        makeInheritedHDF(data, i, superCtp);
+        i++;
+      }
     }
   }
 
-  private static void addInterfaces(ArrayList<ClassInfo> ifaces, Set<ClassInfo> out) {
-    for (ClassInfo cl : ifaces) {
-      out.add(cl);
-      addInterfaces(cl.interfaces(), out);
-    }
-  }
-
-  private static void makeInheritedHDF(Data data, int index, ClassInfo cl) {
+  private static void makeInheritedHDF(Data data, int index, ClassTypePair ctp) {
     int i;
 
     String base = "class.inherited." + index;
-    data.setValue(base + ".qualified", cl.qualifiedName());
-    if (cl.checkLevel()) {
-      data.setValue(base + ".link", cl.htmlPage());
+    data.setValue(base + ".qualified", ctp.classInfo().qualifiedName());
+    if (ctp.classInfo().checkLevel()) {
+      data.setValue(base + ".link", ctp.classInfo().htmlPage());
     }
-    String kind = cl.kind();
+    String kind = ctp.classInfo().kind();
     if (kind != null) {
       data.setValue(base + ".kind", kind);
     }
 
-    if (cl.mIsIncluded) {
+    if (ctp.classInfo().mIsIncluded) {
       data.setValue(base + ".included", "true");
     } else {
-      Doclava.federationTagger.tagAll(new ClassInfo[] {cl});
-      if (!cl.getFederatedReferences().isEmpty()) {
-        FederatedSite site = cl.getFederatedReferences().iterator().next();
-        data.setValue(base + ".link", site.linkFor(cl.htmlPage()));
+      Doclava.federationTagger.tagAll(new ClassInfo[] {ctp.classInfo()});
+      if (!ctp.classInfo().getFederatedReferences().isEmpty()) {
+        FederatedSite site = ctp.classInfo().getFederatedReferences().iterator().next();
+        data.setValue(base + ".link", site.linkFor(ctp.classInfo().htmlPage()));
         data.setValue(base + ".federated", site.name());
       }
     }
 
     // xml attributes
     i = 0;
-    for (AttributeInfo attr : cl.selfAttributes()) {
+    for (AttributeInfo attr : ctp.classInfo().selfAttributes()) {
       attr.makeHDF(data, base + ".attrs." + i);
       i++;
     }
 
     // methods
     i = 0;
-    for (MethodInfo method : cl.selfMethods()) {
-      method.makeHDF(data, base + ".methods." + i);
+    for (MethodInfo method : ctp.classInfo().selfMethods()) {
+      method.makeHDF(data, base + ".methods." + i, ctp.getTypeArgumentMapping());
       i++;
     }
 
     // fields
     i = 0;
-    for (FieldInfo field : cl.selfFields()) {
+    for (FieldInfo field : ctp.classInfo().selfFields()) {
       if (!field.isConstant()) {
         field.makeHDF(data, base + ".fields." + i);
         i++;
@@ -1353,7 +1510,7 @@
 
     // constants
     i = 0;
-    for (FieldInfo field : cl.selfFields()) {
+    for (FieldInfo field : ctp.classInfo().selfFields()) {
       if (field.isConstant()) {
         field.makeHDF(data, base + ".constants." + i);
         i++;
@@ -1727,6 +1884,9 @@
   private boolean mDeprecatedKnown;
 
   // lazy
+  private ArrayList<ClassTypePair> mSuperclassesWithTypes;
+  private ArrayList<ClassTypePair> mInterfacesWithTypes;
+  private ArrayList<ClassTypePair> mAllInterfacesWithTypes;
   private ArrayList<MethodInfo> mConstructors;
   private ArrayList<ClassInfo> mRealInnerClasses;
   private ArrayList<MethodInfo> mSelfMethods;
diff --git a/src/com/google/doclava/Comment.java b/src/com/google/doclava/Comment.java
index c93cda7..cfb7aaf 100644
--- a/src/com/google/doclava/Comment.java
+++ b/src/com/google/doclava/Comment.java
@@ -310,8 +310,10 @@
       mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos));
     } else if (name.equals("@see")) {
       mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos));
-    } else if (name.equals("@link") || name.equals("@linkplain")) {
+    } else if (name.equals("@link")) {
       mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos));
+    } else if (name.equals("@linkplain")) {
+      mInlineTagsList.add(new SeeTagInfo(name, "@linkplain", text, mBase, pos));
     } else if (name.equals("@value")) {
       mInlineTagsList.add(new SeeTagInfo(name, "@value", text, mBase, pos));
     } else if (name.equals("@throws") || name.equals("@exception")) {
diff --git a/src/com/google/doclava/DocFile.java b/src/com/google/doclava/DocFile.java
index 2ebf7b7..fb7c706 100644
--- a/src/com/google/doclava/DocFile.java
+++ b/src/com/google/doclava/DocFile.java
@@ -219,6 +219,8 @@
           hdf.setValue("engage", "true");
         } else if (filename.indexOf("distribute/monetize") == 0) {
           hdf.setValue("monetize", "true");
+        } else if (filename.indexOf("distribute/analyze") == 0) {
+          hdf.setValue("analyze", "true");
         } else if (filename.indexOf("distribute/tools") == 0) {
           hdf.setValue("disttools", "true");
         } else if (filename.indexOf("distribute/stories") == 0) {
diff --git a/src/com/google/doclava/MethodInfo.java b/src/com/google/doclava/MethodInfo.java
index 8c5e271..eb360cd 100644
--- a/src/com/google/doclava/MethodInfo.java
+++ b/src/com/google/doclava/MethodInfo.java
@@ -237,13 +237,23 @@
     return mTypeParameters;
   }
 
-  public MethodInfo cloneForClass(ClassInfo newContainingClass) {
+  /**
+   * Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the
+   * typeArgumentMapping to the parameters and return types.
+   */
+  public MethodInfo cloneForClass(ClassInfo newContainingClass,
+      Map<String, TypeInfo> typeArgumentMapping) {
+    TypeInfo returnType = mReturnType.getTypeWithArguments(typeArgumentMapping);
+    ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
+    for (ParameterInfo pi : mParameters) {
+      parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping));
+    }
     MethodInfo result =
         new MethodInfo(getRawCommentText(), mTypeParameters, name(), signature(),
             newContainingClass, realContainingClass(), isPublic(), isProtected(),
             isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isSynthetic(), mIsAbstract,
             mIsSynchronized, mIsNative, mIsAnnotationElement, kind(), mFlatSignature,
-            mOverriddenMethod, mReturnType, mParameters, mThrownExceptions, position(),
+            mOverriddenMethod, returnType, mParameters, mThrownExceptions, position(),
             annotations());
     result.init(mDefaultAnnotationElementValue);
     return result;
@@ -539,13 +549,18 @@
   }
 
   public void makeHDF(Data data, String base) {
+    makeHDF(data, base, Collections.<String, TypeInfo>emptyMap());
+  }
+
+  public void makeHDF(Data data, String base, Map<String, TypeInfo> typeMapping) {
     data.setValue(base + ".kind", kind());
     data.setValue(base + ".name", name());
     data.setValue(base + ".href", htmlPage());
     data.setValue(base + ".anchor", anchor());
 
     if (mReturnType != null) {
-      returnType().makeHDF(data, base + ".returnType", false, typeVariables());
+      returnType().getTypeWithArguments(typeMapping).makeHDF(
+          data, base + ".returnType", false, typeVariables());
       data.setValue(base + ".abstract", mIsAbstract ? "abstract" : "");
     }
 
@@ -564,7 +579,8 @@
     ParamTagInfo.makeHDF(data, base + ".paramTags", paramTags());
     AttrTagInfo.makeReferenceHDF(data, base + ".attrRefs", comment().attrTags());
     ThrowsTagInfo.makeHDF(data, base + ".throws", throwsTags());
-    ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables());
+    ParameterInfo.makeHDF(data, base + ".params", mParameters.toArray(
+        new ParameterInfo[mParameters.size()]), isVarArgs(), typeVariables(), typeMapping);
     if (isProtected()) {
       data.setValue(base + ".scope", "protected");
     } else if (isPublic()) {
diff --git a/src/com/google/doclava/ParameterInfo.java b/src/com/google/doclava/ParameterInfo.java
index f4e39f0..b911283 100644
--- a/src/com/google/doclava/ParameterInfo.java
+++ b/src/com/google/doclava/ParameterInfo.java
@@ -18,7 +18,9 @@
 
 import com.google.clearsilver.jsilver.data.Data;
 
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 
 public class ParameterInfo {
   public ParameterInfo(String name, String typeName, TypeInfo type, boolean isVarArg,
@@ -30,6 +32,14 @@
     mPosition = position;
   }
 
+  /**
+   * Clone this Parameter, but replace the type according to the typeArgumentMapping provided.
+   */
+  public ParameterInfo cloneWithTypeArguments(Map<String, TypeInfo> typeArgumentMapping) {
+    return new ParameterInfo(
+        mName, mTypeName, mType.getTypeWithArguments(typeArgumentMapping), mIsVarArg, mPosition);
+  }
+
   TypeInfo type() {
     return mType;
   }
@@ -45,23 +55,35 @@
   SourcePositionInfo position() {
     return mPosition;
   }
-  
+
   boolean isVarArg() {
     return mIsVarArg;
   }
 
   public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
+    makeHDF(data, base, isLastVararg, typeVariables, Collections.<String, TypeInfo>emptyMap());
+  }
+
+  public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables,
+      Map<String, TypeInfo> typeMapping) {
     data.setValue(base + ".name", this.name());
-    type().makeHDF(data, base + ".type", isLastVararg, typeVariables);
+    type().getTypeWithArguments(typeMapping).makeHDF(
+        data, base + ".type", isLastVararg, typeVariables);
   }
 
   public static void makeHDF(Data data, String base, ParameterInfo[] params, boolean isVararg,
       HashSet<String> typeVariables) {
+    makeHDF(data, base, params, isVararg, typeVariables, Collections.<String, TypeInfo>emptyMap());
+  }
+
+  public static void makeHDF(Data data, String base, ParameterInfo[] params, boolean isVararg,
+      HashSet<String> typeVariables, Map<String, TypeInfo> typeMapping) {
     for (int i = 0; i < params.length; i++) {
-      params[i].makeHDF(data, base + "." + i, isVararg && (i == params.length - 1), typeVariables);
+      params[i].makeHDF(
+          data, base + "." + i, isVararg && (i == params.length - 1), typeVariables, typeMapping);
     }
   }
-  
+
   /**
    * Returns true if this parameter's dimension information agrees
    * with the represented callee's dimension information.
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index d396e88..2b31321 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -1005,7 +1005,7 @@
     // need to make sure value is valid XML
     String value = makeXMLcompliant(fi.constantLiteralValue());
 
-    String fullTypeName = makeXMLcompliant(fi.type().qualifiedTypeName()) + fi.type().dimension();
+    String fullTypeName = makeXMLcompliant(fi.type().fullName());
 
     xmlWriter.println("<field name=\"" + fi.name() + "\"\n" + " type=\"" + fullTypeName + "\"\n"
         + " transient=\"" + fi.isTransient() + "\"\n" + " volatile=\"" + fi.isVolatile() + "\"\n"
@@ -1358,7 +1358,7 @@
     }
 
     apiWriter.print(" ");
-    apiWriter.print(fi.type().qualifiedTypeName() + fi.type().dimension());
+    apiWriter.print(fi.type().fullName());
 
     apiWriter.print(" ");
     apiWriter.print(fi.name());
diff --git a/src/com/google/doclava/TypeInfo.java b/src/com/google/doclava/TypeInfo.java
index 36d9634..567415b 100644
--- a/src/com/google/doclava/TypeInfo.java
+++ b/src/com/google/doclava/TypeInfo.java
@@ -98,6 +98,29 @@
     }
   }
 
+  /**
+   * Copy Constructor.
+   */
+  private TypeInfo(TypeInfo other) {
+    mIsPrimitive = other.isPrimitive();
+    mIsTypeVariable = other.isTypeVariable();
+    mIsWildcard = other.isWildcard();
+    mDimension = other.dimension();
+    mSimpleTypeName = other.simpleTypeName();
+    mQualifiedTypeName = other.qualifiedTypeName();
+    mClass = other.asClassInfo();
+    if (other.typeArguments() != null) {
+      mTypeArguments = new ArrayList<TypeInfo>(other.typeArguments());
+    }
+    if (other.superBounds() != null) {
+      mSuperBounds = new ArrayList<TypeInfo>(other.superBounds());
+    }
+    if (other.extendsBounds() != null) {
+      mExtendsBounds = new ArrayList<TypeInfo>(other.extendsBounds());
+    }
+    mFullName = other.fullName();
+  }
+
   public ClassInfo asClassInfo() {
     return mClass;
   }
@@ -423,6 +446,51 @@
       return allResolved;
   }
 
+  /**
+   * Copy this TypeInfo, but replace type arguments with those defined in the
+   * typeArguments mapping.
+   * <p>
+   * If the current type is one of the base types in the mapping (i.e. a parameter itself)
+   * then this returns the mapped type.
+   */
+  public TypeInfo getTypeWithArguments(Map<String, TypeInfo> typeArguments) {
+    if (typeArguments.containsKey(fullName())) {
+      return typeArguments.get(fullName());
+    }
+
+    TypeInfo ti = new TypeInfo(this);
+    if (typeArguments() != null) {
+      ArrayList<TypeInfo> newArgs = new ArrayList<TypeInfo>();
+      for (TypeInfo t : typeArguments()) {
+        newArgs.add(t.getTypeWithArguments(typeArguments));
+      }
+      ti.setTypeArguments(newArgs);
+    }
+    return ti;
+  }
+
+  /**
+   * Given two TypeInfos that reference the same type, take the first one's type parameters
+   * and generate a mapping from their names to the type parameters defined in the second.
+   */
+  public static Map<String, TypeInfo> getTypeArgumentMapping(TypeInfo generic, TypeInfo typed) {
+    Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
+    for (int i = 0; i < generic.typeArguments().size(); i++) {
+      if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
+        map.put(generic.typeArguments().get(i).fullName(), typed.typeArguments().get(i));
+      }
+    }
+    return map;
+  }
+
+  /**
+   * Given a ClassInfo and a parameterized TypeInfo, take the class's raw type's type parameters
+   * and generate a mapping from their names to the type parameters defined in the TypeInfo.
+   */
+  public static Map<String, TypeInfo> getTypeArgumentMapping(ClassInfo cls, TypeInfo typed) {
+    return getTypeArgumentMapping(cls.asTypeInfo(), typed);
+  }
+
   private ArrayList<Resolution> mResolutions;
 
   private boolean mIsPrimitive;