Home | History | Annotate | Download | only in doclava
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.doclava;
     18 
     19 import com.google.clearsilver.jsilver.data.Data;
     20 
     21 import java.util.*;
     22 
     23 public class TypeInfo implements Resolvable {
     24   public static final Set<String> PRIMITIVE_TYPES = Collections.unmodifiableSet(
     25       new HashSet<String>(Arrays.asList("boolean", "byte", "char", "double", "float", "int",
     26       "long", "short", "void")));
     27 
     28   public TypeInfo(boolean isPrimitive, String dimension, String simpleTypeName,
     29       String qualifiedTypeName, ClassInfo cl) {
     30     mIsPrimitive = isPrimitive;
     31     mDimension = dimension;
     32     mSimpleTypeName = simpleTypeName;
     33     mQualifiedTypeName = qualifiedTypeName;
     34     mClass = cl;
     35   }
     36 
     37   public TypeInfo(String typeString) {
     38     // VarArgs
     39     if (typeString.endsWith("...")) {
     40       typeString = typeString.substring(0, typeString.length() - 3);
     41     }
     42 
     43     // Generic parameters
     44     int extendsPos = typeString.indexOf(" extends ");
     45     int paramStartPos = typeString.indexOf('<');
     46     if (paramStartPos > -1 && (extendsPos == -1 || paramStartPos < extendsPos)) {
     47       ArrayList<TypeInfo> generics = new ArrayList<TypeInfo>();
     48       int paramEndPos = 0;
     49 
     50       int entryStartPos = paramStartPos + 1;
     51       int bracketNesting = 0;
     52       for (int i = entryStartPos; i < typeString.length(); i++) {
     53         char c = typeString.charAt(i);
     54         if (c == ',' && bracketNesting == 0) {
     55           String entry = typeString.substring(entryStartPos, i).trim();
     56           TypeInfo info = new TypeInfo(entry);
     57           generics.add(info);
     58           entryStartPos = i + 1;
     59         } else if (c == '<') {
     60           bracketNesting++;
     61         } else if (c == '>') {
     62           bracketNesting--;
     63           // Once bracketNesting goes negative, we've found the closing angle bracket
     64           if (bracketNesting < 0) {
     65             paramEndPos = i;
     66             break;
     67           }
     68         }
     69       }
     70 
     71       TypeInfo info = new TypeInfo(typeString.substring(entryStartPos, paramEndPos).trim());
     72       generics.add(info);
     73       addResolution(new Resolution("variability", "", null));
     74 
     75       mTypeArguments = generics;
     76 
     77       if (paramEndPos < typeString.length() - 1) {
     78         typeString = typeString.substring(0,paramStartPos) + typeString.substring(paramEndPos + 1);
     79       } else {
     80         typeString = typeString.substring(0,paramStartPos);
     81       }
     82     }
     83 
     84     // The previous extends may have been within the generic type parameters which we don't
     85     // actually care about and were removed from the type string above
     86     extendsPos = typeString.indexOf(" extends ");
     87     if (extendsPos > -1) {
     88       ArrayList<TypeInfo> extendsBounds = new ArrayList<>();
     89       int entryStartPos = extendsPos + 9;
     90       int bracketNesting = 0;
     91       for (int i = entryStartPos; i < typeString.length(); i++) {
     92         char c = typeString.charAt(i);
     93         if (c == '&' && bracketNesting == 0) {
     94           String entry = typeString.substring(entryStartPos, i).trim();
     95           TypeInfo info = new TypeInfo(entry);
     96           extendsBounds.add(info);
     97           entryStartPos = i + 1;
     98         } else if (c == '<') {
     99           bracketNesting++;
    100         } else if (c == '>') {
    101           bracketNesting--;
    102         }
    103       }
    104       TypeInfo info = new TypeInfo(typeString.substring(entryStartPos, typeString.length()).trim());
    105       extendsBounds.add(info);
    106       mExtendsBounds = extendsBounds;
    107       typeString = typeString.substring(0, extendsPos);
    108     }
    109 
    110     int pos = typeString.indexOf('[');
    111     if (pos > -1) {
    112       mDimension = typeString.substring(pos);
    113       typeString = typeString.substring(0, pos);
    114     } else {
    115       mDimension = "";
    116     }
    117 
    118     if (PRIMITIVE_TYPES.contains(typeString)) {
    119       mIsPrimitive = true;
    120       mSimpleTypeName = typeString;
    121       mQualifiedTypeName = typeString;
    122     } else {
    123       mQualifiedTypeName = typeString;
    124       pos = typeString.lastIndexOf('.');
    125       if (pos > -1) {
    126         mSimpleTypeName = typeString.substring(pos + 1);
    127       } else {
    128         mSimpleTypeName = typeString;
    129       }
    130     }
    131   }
    132 
    133   /**
    134    * Copy Constructor.
    135    */
    136   private TypeInfo(TypeInfo other) {
    137     mIsPrimitive = other.isPrimitive();
    138     mIsTypeVariable = other.isTypeVariable();
    139     mIsWildcard = other.isWildcard();
    140     mDimension = other.dimension();
    141     mSimpleTypeName = other.simpleTypeName();
    142     mQualifiedTypeName = other.qualifiedTypeName();
    143     mClass = other.asClassInfo();
    144     if (other.typeArguments() != null) {
    145       mTypeArguments = new ArrayList<TypeInfo>(other.typeArguments());
    146     }
    147     if (other.superBounds() != null) {
    148       mSuperBounds = new ArrayList<TypeInfo>(other.superBounds());
    149     }
    150     if (other.extendsBounds() != null) {
    151       mExtendsBounds = new ArrayList<TypeInfo>(other.extendsBounds());
    152     }
    153     mFullName = other.fullName();
    154   }
    155 
    156   /**
    157    * Returns this type as a {@link ClassInfo} if it represents a class or
    158    * interface.
    159    */
    160   public ClassInfo asClassInfo() {
    161     if (!mResolvedClass) {
    162       mResolvedClass = true;
    163       if (mClass == null && !mIsPrimitive && !mIsTypeVariable && !mIsWildcard) {
    164         mClass = Converter.obtainClass(qualifiedTypeName());
    165       }
    166     }
    167     return mClass;
    168   }
    169 
    170   public boolean isPrimitive() {
    171     return mIsPrimitive;
    172   }
    173 
    174   public String dimension() {
    175     return mDimension;
    176   }
    177 
    178   public void setDimension(String dimension) {
    179       mDimension = dimension;
    180   }
    181 
    182   public String simpleTypeName() {
    183     return mSimpleTypeName;
    184   }
    185 
    186   public String qualifiedTypeName() {
    187     return mQualifiedTypeName;
    188   }
    189 
    190   public String fullName() {
    191     if (mFullName != null) {
    192       return mFullName;
    193     } else {
    194       return fullName(new HashSet<String>());
    195     }
    196   }
    197 
    198   public static String typeArgumentsName(ArrayList<TypeInfo> args, HashSet<String> typeVars) {
    199     String result = "<";
    200 
    201     int i = 0;
    202     for (TypeInfo arg : args) {
    203       result += arg.fullName(typeVars);
    204       if (i != (args.size()-1)) {
    205         result += ", ";
    206       }
    207       i++;
    208     }
    209     result += ">";
    210     return result;
    211   }
    212 
    213   public String fullName(HashSet<String> typeVars) {
    214     mFullName = fullNameNoDimension(typeVars) + mDimension;
    215     return mFullName;
    216   }
    217 
    218   public String fullNameNoBounds(HashSet<String> typeVars) {
    219     return fullNameNoDimensionNoBounds(typeVars) + mDimension;
    220   }
    221 
    222   // don't recurse forever with the parameters. This handles
    223   // Enum<K extends Enum<K>>
    224   private boolean checkRecurringTypeVar(HashSet<String> typeVars) {
    225     if (mIsTypeVariable) {
    226       if (typeVars.contains(mQualifiedTypeName)) {
    227         return true;
    228       }
    229       typeVars.add(mQualifiedTypeName);
    230     }
    231     return false;
    232   }
    233 
    234   private String fullNameNoDimensionNoBounds(HashSet<String> typeVars) {
    235     String fullName = null;
    236     if (checkRecurringTypeVar(typeVars)) {
    237       return mQualifiedTypeName;
    238     }
    239     /*
    240      * if (fullName != null) { return fullName; }
    241      */
    242     fullName = mQualifiedTypeName;
    243     if (mTypeArguments != null && !mTypeArguments.isEmpty()) {
    244       fullName += typeArgumentsName(mTypeArguments, typeVars);
    245     }
    246     return fullName;
    247   }
    248 
    249   public String fullNameNoDimension(HashSet<String> typeVars) {
    250     String fullName = null;
    251     if (checkRecurringTypeVar(typeVars)) {
    252       return mQualifiedTypeName;
    253     }
    254     fullName = fullNameNoDimensionNoBounds(typeVars);
    255     if (mTypeArguments == null || mTypeArguments.isEmpty()) {
    256        if (mSuperBounds != null && !mSuperBounds.isEmpty()) {
    257         for (TypeInfo superBound : mSuperBounds) {
    258             if (superBound == mSuperBounds.get(0)) {
    259                 fullName += " super " + superBound.fullNameNoBounds(typeVars);
    260             } else {
    261                 fullName += " & " + superBound.fullNameNoBounds(typeVars);
    262             }
    263         }
    264       } else if (mExtendsBounds != null && !mExtendsBounds.isEmpty()) {
    265         for (TypeInfo extendsBound : mExtendsBounds) {
    266             if (extendsBound == mExtendsBounds.get(0)) {
    267                 fullName += " extends " + extendsBound.fullNameNoBounds(typeVars);
    268             } else {
    269                 fullName += " & " + extendsBound.fullNameNoBounds(typeVars);
    270             }
    271         }
    272       }
    273     }
    274     return fullName;
    275   }
    276 
    277   public ArrayList<TypeInfo> typeArguments() {
    278     return mTypeArguments;
    279   }
    280 
    281   public void makeHDF(Data data, String base) {
    282     makeHDFRecursive(data, base, false, false, new HashSet<String>());
    283   }
    284 
    285   public void makeQualifiedHDF(Data data, String base) {
    286     makeHDFRecursive(data, base, true, false, new HashSet<String>());
    287   }
    288 
    289   public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
    290     makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
    291   }
    292 
    293   public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
    294     makeHDFRecursive(data, base, true, false, typeVariables);
    295   }
    296 
    297   private void makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg,
    298       HashSet<String> typeVars) {
    299     String label = qualified ? qualifiedTypeName() : simpleTypeName();
    300     label += (isLastVararg) ? "..." : dimension();
    301     data.setValue(base + ".label", label);
    302     if (mIsTypeVariable || mIsWildcard) {
    303       // could link to an @param tag on the class to describe this
    304       // but for now, just don't make it a link
    305     } else if (!isPrimitive() && mClass != null) {
    306       if (mClass.isIncluded()) {
    307         data.setValue(base + ".link", mClass.htmlPage());
    308         data.setValue(base + ".since", mClass.getSince());
    309       } else {
    310         Doclava.federationTagger.tag(mClass);
    311         if (!mClass.getFederatedReferences().isEmpty()) {
    312           FederatedSite site = mClass.getFederatedReferences().iterator().next();
    313           data.setValue(base + ".link", site.linkFor(mClass.htmlPage()));
    314           data.setValue(base + ".federated", site.name());
    315         }
    316       }
    317     }
    318 
    319     if (mIsTypeVariable) {
    320       if (typeVars.contains(qualifiedTypeName())) {
    321         // don't recurse forever with the parameters. This handles
    322         // Enum<K extends Enum<K>>
    323         return;
    324       }
    325       typeVars.add(qualifiedTypeName());
    326     }
    327     if (mTypeArguments != null) {
    328       TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars);
    329     }
    330     if (mSuperBounds != null) {
    331       TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars);
    332     }
    333     if (mExtendsBounds != null) {
    334       TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars);
    335     }
    336   }
    337 
    338   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified,
    339       HashSet<String> typeVariables) {
    340     int i = 0;
    341     for (TypeInfo type : types) {
    342       type.makeHDFRecursive(data, base + "." + i++, qualified, false, typeVariables);
    343     }
    344   }
    345 
    346   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified) {
    347     makeHDF(data, base, types, qualified, new HashSet<String>());
    348   }
    349 
    350   void setTypeArguments(ArrayList<TypeInfo> args) {
    351     mTypeArguments = args;
    352   }
    353 
    354   public void addTypeArgument(TypeInfo arg) {
    355       if (mTypeArguments == null) {
    356           mTypeArguments = new ArrayList<TypeInfo>();
    357       }
    358 
    359       mTypeArguments.add(arg);
    360   }
    361 
    362   public void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
    363     mSuperBounds = superBounds;
    364     mExtendsBounds = extendsBounds;
    365   }
    366 
    367   public ArrayList<TypeInfo> superBounds() {
    368       return mSuperBounds;
    369   }
    370 
    371   public ArrayList<TypeInfo> extendsBounds() {
    372       return mExtendsBounds;
    373   }
    374 
    375   public void setIsTypeVariable(boolean b) {
    376     mIsTypeVariable = b;
    377   }
    378 
    379   void setIsWildcard(boolean b) {
    380     mIsWildcard = b;
    381   }
    382 
    383   public boolean isWildcard() {
    384       return mIsWildcard;
    385   }
    386 
    387   public static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
    388     return typeVariables(params, new HashSet<String>());
    389   }
    390 
    391   static HashSet<String> typeVariables(ArrayList<TypeInfo> params, HashSet<String> result) {
    392     if (params != null) {
    393         for (TypeInfo t : params) {
    394             if (t.mIsTypeVariable) {
    395                 result.add(t.mQualifiedTypeName);
    396             }
    397         }
    398     }
    399     return result;
    400   }
    401 
    402 
    403   public boolean isTypeVariable() {
    404     return mIsTypeVariable;
    405   }
    406 
    407   public void resolveTypeVariables(HashSet<String> variables) {
    408     if (mExtendsBounds != null) {
    409       for (TypeInfo bound : mExtendsBounds) {
    410         if (variables.contains(bound.qualifiedTypeName())) {
    411           bound.setIsTypeVariable(true);
    412         }
    413       }
    414     }
    415   }
    416 
    417   public String defaultValue() {
    418     if (mIsPrimitive) {
    419       if ("boolean".equals(mSimpleTypeName)) {
    420         return "false";
    421       } else {
    422         return "0";
    423       }
    424     } else {
    425       return "null";
    426     }
    427   }
    428 
    429   @Override
    430   public String toString() {
    431     String returnString = "";
    432     returnString +=
    433         "Primitive?: " + mIsPrimitive + " TypeVariable?: " + mIsTypeVariable + " Wildcard?: "
    434             + mIsWildcard + " Dimension: " + mDimension + " QualifedTypeName: "
    435             + mQualifiedTypeName;
    436 
    437     if (mTypeArguments != null) {
    438       returnString += "\nTypeArguments: ";
    439       for (TypeInfo tA : mTypeArguments) {
    440         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    441       }
    442     }
    443     if (mSuperBounds != null) {
    444       returnString += "\nSuperBounds: ";
    445       for (TypeInfo tA : mSuperBounds) {
    446         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    447       }
    448     }
    449     if (mExtendsBounds != null) {
    450       returnString += "\nExtendsBounds: ";
    451       for (TypeInfo tA : mExtendsBounds) {
    452         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    453       }
    454     }
    455     return returnString;
    456   }
    457 
    458   public void addResolution(Resolution resolution) {
    459       if (mResolutions == null) {
    460           mResolutions = new ArrayList<Resolution>();
    461       }
    462 
    463       mResolutions.add(resolution);
    464   }
    465 
    466   public void printResolutions() {
    467       if (mResolutions == null || mResolutions.isEmpty()) {
    468           return;
    469       }
    470 
    471       System.out.println("Resolutions for Type " + mSimpleTypeName + ":");
    472       for (Resolution r : mResolutions) {
    473           System.out.println(r);
    474       }
    475   }
    476 
    477   public boolean resolveResolutions() {
    478       ArrayList<Resolution> resolutions = mResolutions;
    479       mResolutions = new ArrayList<Resolution>();
    480 
    481       boolean allResolved = true;
    482       for (Resolution resolution : resolutions) {
    483           if ("class".equals(resolution.getVariable())) {
    484               StringBuilder qualifiedClassName = new StringBuilder();
    485               InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
    486                       resolution.getInfoBuilder());
    487 
    488               // if we still couldn't resolve it, save it for the next pass
    489               if ("".equals(qualifiedClassName.toString())) {
    490                   mResolutions.add(resolution);
    491                   allResolved = false;
    492               } else {
    493                   mClass = InfoBuilder.Caches.obtainClass(qualifiedClassName.toString());
    494               }
    495           } else if ("variability".equals(resolution.getVariable())) {
    496               StringBuilder qualifiedClassName = new StringBuilder();
    497               for (TypeInfo arg : mTypeArguments) {
    498                 InfoBuilder.resolveQualifiedName(arg.simpleTypeName(), qualifiedClassName,
    499                         resolution.getInfoBuilder());
    500                 arg.setIsTypeVariable(!("".equals(qualifiedClassName.toString())));
    501               }
    502           }
    503       }
    504 
    505       return allResolved;
    506   }
    507 
    508   /**
    509    * Copy this TypeInfo, but replace type arguments with those defined in the
    510    * typeArguments mapping.
    511    * <p>
    512    * If the current type is one of the base types in the mapping (i.e. a parameter itself)
    513    * then this returns the mapped type.
    514    */
    515   public TypeInfo getTypeWithArguments(Map<String, TypeInfo> typeArguments) {
    516     if (typeArguments.containsKey(fullName())) {
    517       return typeArguments.get(fullName());
    518     }
    519 
    520     TypeInfo ti = new TypeInfo(this);
    521     if (typeArguments() != null) {
    522       ArrayList<TypeInfo> newArgs = new ArrayList<TypeInfo>();
    523       for (TypeInfo t : typeArguments()) {
    524         newArgs.add(t.getTypeWithArguments(typeArguments));
    525       }
    526       ti.setTypeArguments(newArgs);
    527     }
    528     return ti;
    529   }
    530 
    531   /**
    532    * Given two TypeInfos that reference the same type, take the first one's type parameters
    533    * and generate a mapping from their names to the type parameters defined in the second.
    534    */
    535   public static Map<String, TypeInfo> getTypeArgumentMapping(TypeInfo generic, TypeInfo typed) {
    536     Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
    537     if (generic != null && generic.typeArguments() != null) {
    538       for (int i = 0; i < generic.typeArguments().size(); i++) {
    539         if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
    540           map.put(generic.typeArguments().get(i).simpleTypeName(), typed.typeArguments().get(i));
    541         }
    542       }
    543     }
    544     return map;
    545   }
    546 
    547   /**
    548    * Given a ClassInfo and a parameterized TypeInfo, take the class's raw type's type parameters
    549    * and generate a mapping from their names to the type parameters defined in the TypeInfo.
    550    */
    551   public static Map<String, TypeInfo> getTypeArgumentMapping(ClassInfo cls, TypeInfo typed) {
    552     return getTypeArgumentMapping(cls.asTypeInfo(), typed);
    553   }
    554 
    555   private ArrayList<Resolution> mResolutions;
    556 
    557   /** Whether the value of {@code mClass} has been resolved. */
    558   private boolean mResolvedClass;
    559 
    560   private boolean mIsPrimitive;
    561   private boolean mIsTypeVariable;
    562   private boolean mIsWildcard;
    563   private String mDimension;
    564   private String mSimpleTypeName;
    565   private String mQualifiedTypeName;
    566   private ClassInfo mClass;
    567   private ArrayList<TypeInfo> mTypeArguments;
    568   private ArrayList<TypeInfo> mSuperBounds;
    569   private ArrayList<TypeInfo> mExtendsBounds;
    570   private String mFullName;
    571 }
    572