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   public ClassInfo asClassInfo() {
    157     return mClass;
    158   }
    159 
    160   public boolean isPrimitive() {
    161     return mIsPrimitive;
    162   }
    163 
    164   public String dimension() {
    165     return mDimension;
    166   }
    167 
    168   public void setDimension(String dimension) {
    169       mDimension = dimension;
    170   }
    171 
    172   public String simpleTypeName() {
    173     return mSimpleTypeName;
    174   }
    175 
    176   public String qualifiedTypeName() {
    177     return mQualifiedTypeName;
    178   }
    179 
    180   public String fullName() {
    181     if (mFullName != null) {
    182       return mFullName;
    183     } else {
    184       return fullName(new HashSet<String>());
    185     }
    186   }
    187 
    188   public static String typeArgumentsName(ArrayList<TypeInfo> args, HashSet<String> typeVars) {
    189     String result = "<";
    190 
    191     int i = 0;
    192     for (TypeInfo arg : args) {
    193       result += arg.fullName(typeVars);
    194       if (i != (args.size()-1)) {
    195         result += ", ";
    196       }
    197       i++;
    198     }
    199     result += ">";
    200     return result;
    201   }
    202 
    203   public String fullName(HashSet<String> typeVars) {
    204     mFullName = fullNameNoDimension(typeVars) + mDimension;
    205     return mFullName;
    206   }
    207 
    208   public String fullNameNoBounds(HashSet<String> typeVars) {
    209     return fullNameNoDimensionNoBounds(typeVars) + mDimension;
    210   }
    211 
    212   // don't recurse forever with the parameters. This handles
    213   // Enum<K extends Enum<K>>
    214   private boolean checkRecurringTypeVar(HashSet<String> typeVars) {
    215     if (mIsTypeVariable) {
    216       if (typeVars.contains(mQualifiedTypeName)) {
    217         return true;
    218       }
    219       typeVars.add(mQualifiedTypeName);
    220     }
    221     return false;
    222   }
    223 
    224   private String fullNameNoDimensionNoBounds(HashSet<String> typeVars) {
    225     String fullName = null;
    226     if (checkRecurringTypeVar(typeVars)) {
    227       return mQualifiedTypeName;
    228     }
    229     /*
    230      * if (fullName != null) { return fullName; }
    231      */
    232     fullName = mQualifiedTypeName;
    233     if (mTypeArguments != null && !mTypeArguments.isEmpty()) {
    234       fullName += typeArgumentsName(mTypeArguments, typeVars);
    235     }
    236     return fullName;
    237   }
    238 
    239   public String fullNameNoDimension(HashSet<String> typeVars) {
    240     String fullName = null;
    241     if (checkRecurringTypeVar(typeVars)) {
    242       return mQualifiedTypeName;
    243     }
    244     fullName = fullNameNoDimensionNoBounds(typeVars);
    245     if (mTypeArguments == null || mTypeArguments.isEmpty()) {
    246        if (mSuperBounds != null && !mSuperBounds.isEmpty()) {
    247         for (TypeInfo superBound : mSuperBounds) {
    248             if (superBound == mSuperBounds.get(0)) {
    249                 fullName += " super " + superBound.fullNameNoBounds(typeVars);
    250             } else {
    251                 fullName += " & " + superBound.fullNameNoBounds(typeVars);
    252             }
    253         }
    254       } else if (mExtendsBounds != null && !mExtendsBounds.isEmpty()) {
    255         for (TypeInfo extendsBound : mExtendsBounds) {
    256             if (extendsBound == mExtendsBounds.get(0)) {
    257                 fullName += " extends " + extendsBound.fullNameNoBounds(typeVars);
    258             } else {
    259                 fullName += " & " + extendsBound.fullNameNoBounds(typeVars);
    260             }
    261         }
    262       }
    263     }
    264     return fullName;
    265   }
    266 
    267   public ArrayList<TypeInfo> typeArguments() {
    268     return mTypeArguments;
    269   }
    270 
    271   public void makeHDF(Data data, String base) {
    272     makeHDFRecursive(data, base, false, false, new HashSet<String>());
    273   }
    274 
    275   public void makeQualifiedHDF(Data data, String base) {
    276     makeHDFRecursive(data, base, true, false, new HashSet<String>());
    277   }
    278 
    279   public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
    280     makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
    281   }
    282 
    283   public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
    284     makeHDFRecursive(data, base, true, false, typeVariables);
    285   }
    286 
    287   private void makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg,
    288       HashSet<String> typeVars) {
    289     String label = qualified ? qualifiedTypeName() : simpleTypeName();
    290     label += (isLastVararg) ? "..." : dimension();
    291     data.setValue(base + ".label", label);
    292     if (mIsTypeVariable || mIsWildcard) {
    293       // could link to an @param tag on the class to describe this
    294       // but for now, just don't make it a link
    295     } else if (!isPrimitive() && mClass != null) {
    296       if (mClass.isIncluded()) {
    297         data.setValue(base + ".link", mClass.htmlPage());
    298         data.setValue(base + ".since", mClass.getSince());
    299       } else {
    300         Doclava.federationTagger.tag(mClass);
    301         if (!mClass.getFederatedReferences().isEmpty()) {
    302           FederatedSite site = mClass.getFederatedReferences().iterator().next();
    303           data.setValue(base + ".link", site.linkFor(mClass.htmlPage()));
    304           data.setValue(base + ".federated", site.name());
    305         }
    306       }
    307     }
    308 
    309     if (mIsTypeVariable) {
    310       if (typeVars.contains(qualifiedTypeName())) {
    311         // don't recurse forever with the parameters. This handles
    312         // Enum<K extends Enum<K>>
    313         return;
    314       }
    315       typeVars.add(qualifiedTypeName());
    316     }
    317     if (mTypeArguments != null) {
    318       TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars);
    319     }
    320     if (mSuperBounds != null) {
    321       TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars);
    322     }
    323     if (mExtendsBounds != null) {
    324       TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars);
    325     }
    326   }
    327 
    328   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified,
    329       HashSet<String> typeVariables) {
    330     int i = 0;
    331     for (TypeInfo type : types) {
    332       type.makeHDFRecursive(data, base + "." + i++, qualified, false, typeVariables);
    333     }
    334   }
    335 
    336   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified) {
    337     makeHDF(data, base, types, qualified, new HashSet<String>());
    338   }
    339 
    340   void setTypeArguments(ArrayList<TypeInfo> args) {
    341     mTypeArguments = args;
    342   }
    343 
    344   public void addTypeArgument(TypeInfo arg) {
    345       if (mTypeArguments == null) {
    346           mTypeArguments = new ArrayList<TypeInfo>();
    347       }
    348 
    349       mTypeArguments.add(arg);
    350   }
    351 
    352   public void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
    353     mSuperBounds = superBounds;
    354     mExtendsBounds = extendsBounds;
    355   }
    356 
    357   public ArrayList<TypeInfo> superBounds() {
    358       return mSuperBounds;
    359   }
    360 
    361   public ArrayList<TypeInfo> extendsBounds() {
    362       return mExtendsBounds;
    363   }
    364 
    365   public void setIsTypeVariable(boolean b) {
    366     mIsTypeVariable = b;
    367   }
    368 
    369   void setIsWildcard(boolean b) {
    370     mIsWildcard = b;
    371   }
    372 
    373   public boolean isWildcard() {
    374       return mIsWildcard;
    375   }
    376 
    377   public static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
    378     return typeVariables(params, new HashSet<String>());
    379   }
    380 
    381   static HashSet<String> typeVariables(ArrayList<TypeInfo> params, HashSet<String> result) {
    382     if (params != null) {
    383         for (TypeInfo t : params) {
    384             if (t.mIsTypeVariable) {
    385                 result.add(t.mQualifiedTypeName);
    386             }
    387         }
    388     }
    389     return result;
    390   }
    391 
    392 
    393   public boolean isTypeVariable() {
    394     return mIsTypeVariable;
    395   }
    396 
    397   public void resolveTypeVariables(HashSet<String> variables) {
    398     if (mExtendsBounds != null) {
    399       for (TypeInfo bound : mExtendsBounds) {
    400         if (variables.contains(bound.qualifiedTypeName())) {
    401           bound.setIsTypeVariable(true);
    402         }
    403       }
    404     }
    405   }
    406 
    407   public String defaultValue() {
    408     if (mIsPrimitive) {
    409       if ("boolean".equals(mSimpleTypeName)) {
    410         return "false";
    411       } else {
    412         return "0";
    413       }
    414     } else {
    415       return "null";
    416     }
    417   }
    418 
    419   @Override
    420   public String toString() {
    421     String returnString = "";
    422     returnString +=
    423         "Primitive?: " + mIsPrimitive + " TypeVariable?: " + mIsTypeVariable + " Wildcard?: "
    424             + mIsWildcard + " Dimension: " + mDimension + " QualifedTypeName: "
    425             + mQualifiedTypeName;
    426 
    427     if (mTypeArguments != null) {
    428       returnString += "\nTypeArguments: ";
    429       for (TypeInfo tA : mTypeArguments) {
    430         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    431       }
    432     }
    433     if (mSuperBounds != null) {
    434       returnString += "\nSuperBounds: ";
    435       for (TypeInfo tA : mSuperBounds) {
    436         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    437       }
    438     }
    439     if (mExtendsBounds != null) {
    440       returnString += "\nExtendsBounds: ";
    441       for (TypeInfo tA : mExtendsBounds) {
    442         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    443       }
    444     }
    445     return returnString;
    446   }
    447 
    448   public void addResolution(Resolution resolution) {
    449       if (mResolutions == null) {
    450           mResolutions = new ArrayList<Resolution>();
    451       }
    452 
    453       mResolutions.add(resolution);
    454   }
    455 
    456   public void printResolutions() {
    457       if (mResolutions == null || mResolutions.isEmpty()) {
    458           return;
    459       }
    460 
    461       System.out.println("Resolutions for Type " + mSimpleTypeName + ":");
    462       for (Resolution r : mResolutions) {
    463           System.out.println(r);
    464       }
    465   }
    466 
    467   public boolean resolveResolutions() {
    468       ArrayList<Resolution> resolutions = mResolutions;
    469       mResolutions = new ArrayList<Resolution>();
    470 
    471       boolean allResolved = true;
    472       for (Resolution resolution : resolutions) {
    473           if ("class".equals(resolution.getVariable())) {
    474               StringBuilder qualifiedClassName = new StringBuilder();
    475               InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
    476                       resolution.getInfoBuilder());
    477 
    478               // if we still couldn't resolve it, save it for the next pass
    479               if ("".equals(qualifiedClassName.toString())) {
    480                   mResolutions.add(resolution);
    481                   allResolved = false;
    482               } else {
    483                   mClass = InfoBuilder.Caches.obtainClass(qualifiedClassName.toString());
    484               }
    485           } else if ("variability".equals(resolution.getVariable())) {
    486               StringBuilder qualifiedClassName = new StringBuilder();
    487               for (TypeInfo arg : mTypeArguments) {
    488                 InfoBuilder.resolveQualifiedName(arg.simpleTypeName(), qualifiedClassName,
    489                         resolution.getInfoBuilder());
    490                 arg.setIsTypeVariable(!("".equals(qualifiedClassName.toString())));
    491               }
    492           }
    493       }
    494 
    495       return allResolved;
    496   }
    497 
    498   /**
    499    * Copy this TypeInfo, but replace type arguments with those defined in the
    500    * typeArguments mapping.
    501    * <p>
    502    * If the current type is one of the base types in the mapping (i.e. a parameter itself)
    503    * then this returns the mapped type.
    504    */
    505   public TypeInfo getTypeWithArguments(Map<String, TypeInfo> typeArguments) {
    506     if (typeArguments.containsKey(fullName())) {
    507       return typeArguments.get(fullName());
    508     }
    509 
    510     TypeInfo ti = new TypeInfo(this);
    511     if (typeArguments() != null) {
    512       ArrayList<TypeInfo> newArgs = new ArrayList<TypeInfo>();
    513       for (TypeInfo t : typeArguments()) {
    514         newArgs.add(t.getTypeWithArguments(typeArguments));
    515       }
    516       ti.setTypeArguments(newArgs);
    517     }
    518     return ti;
    519   }
    520 
    521   /**
    522    * Given two TypeInfos that reference the same type, take the first one's type parameters
    523    * and generate a mapping from their names to the type parameters defined in the second.
    524    */
    525   public static Map<String, TypeInfo> getTypeArgumentMapping(TypeInfo generic, TypeInfo typed) {
    526     Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
    527     if (generic != null && generic.typeArguments() != null) {
    528       for (int i = 0; i < generic.typeArguments().size(); i++) {
    529         if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
    530           map.put(generic.typeArguments().get(i).simpleTypeName(), typed.typeArguments().get(i));
    531         }
    532       }
    533     }
    534     return map;
    535   }
    536 
    537   /**
    538    * Given a ClassInfo and a parameterized TypeInfo, take the class's raw type's type parameters
    539    * and generate a mapping from their names to the type parameters defined in the TypeInfo.
    540    */
    541   public static Map<String, TypeInfo> getTypeArgumentMapping(ClassInfo cls, TypeInfo typed) {
    542     return getTypeArgumentMapping(cls.asTypeInfo(), typed);
    543   }
    544 
    545   private ArrayList<Resolution> mResolutions;
    546 
    547   private boolean mIsPrimitive;
    548   private boolean mIsTypeVariable;
    549   private boolean mIsWildcard;
    550   private String mDimension;
    551   private String mSimpleTypeName;
    552   private String mQualifiedTypeName;
    553   private ClassInfo mClass;
    554   private ArrayList<TypeInfo> mTypeArguments;
    555   private ArrayList<TypeInfo> mSuperBounds;
    556   private ArrayList<TypeInfo> mExtendsBounds;
    557   private String mFullName;
    558 }
    559