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 String dexName() {
    278     if (mIsTypeVariable || mIsWildcard) {
    279       if (mExtendsBounds != null && !mExtendsBounds.isEmpty()) {
    280         return mExtendsBounds.get(0).dexName() + mDimension;
    281       } else {
    282         return "java.lang.Object" + mDimension;
    283       }
    284     }
    285     return mQualifiedTypeName + mDimension;
    286   }
    287 
    288   public ArrayList<TypeInfo> typeArguments() {
    289     return mTypeArguments;
    290   }
    291 
    292   public void makeHDF(Data data, String base) {
    293     makeHDFRecursive(data, base, false, false, new HashSet<String>());
    294   }
    295 
    296   public void makeQualifiedHDF(Data data, String base) {
    297     makeHDFRecursive(data, base, true, false, new HashSet<String>());
    298   }
    299 
    300   public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
    301     makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
    302   }
    303 
    304   public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
    305     makeHDFRecursive(data, base, true, false, typeVariables);
    306   }
    307 
    308   private void makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg,
    309       HashSet<String> typeVars) {
    310     String label = qualified ? qualifiedTypeName() : simpleTypeName();
    311     label += (isLastVararg) ? "..." : dimension();
    312     data.setValue(base + ".label", label);
    313     if (mIsTypeVariable || mIsWildcard) {
    314       // could link to an @param tag on the class to describe this
    315       // but for now, just don't make it a link
    316     } else if (!isPrimitive() && mClass != null) {
    317       if (mClass.isIncluded()) {
    318         data.setValue(base + ".link", mClass.htmlPage());
    319         data.setValue(base + ".since", mClass.getSince());
    320       } else {
    321         Doclava.federationTagger.tag(mClass);
    322         if (!mClass.getFederatedReferences().isEmpty()) {
    323           FederatedSite site = mClass.getFederatedReferences().iterator().next();
    324           data.setValue(base + ".link", site.linkFor(mClass.htmlPage()));
    325           data.setValue(base + ".federated", site.name());
    326         }
    327       }
    328     }
    329 
    330     if (mIsTypeVariable) {
    331       if (typeVars.contains(qualifiedTypeName())) {
    332         // don't recurse forever with the parameters. This handles
    333         // Enum<K extends Enum<K>>
    334         return;
    335       }
    336       typeVars.add(qualifiedTypeName());
    337     }
    338     if (mTypeArguments != null) {
    339       TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars);
    340     }
    341     if (mSuperBounds != null) {
    342       TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars);
    343     }
    344     if (mExtendsBounds != null) {
    345       TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars);
    346     }
    347   }
    348 
    349   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified,
    350       HashSet<String> typeVariables) {
    351     int i = 0;
    352     for (TypeInfo type : types) {
    353       type.makeHDFRecursive(data, base + "." + i++, qualified, false, typeVariables);
    354     }
    355   }
    356 
    357   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified) {
    358     makeHDF(data, base, types, qualified, new HashSet<String>());
    359   }
    360 
    361   void setTypeArguments(ArrayList<TypeInfo> args) {
    362     mTypeArguments = args;
    363   }
    364 
    365   public void addTypeArgument(TypeInfo arg) {
    366       if (mTypeArguments == null) {
    367           mTypeArguments = new ArrayList<TypeInfo>();
    368       }
    369 
    370       mTypeArguments.add(arg);
    371   }
    372 
    373   public void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
    374     mSuperBounds = superBounds;
    375     mExtendsBounds = extendsBounds;
    376   }
    377 
    378   public ArrayList<TypeInfo> superBounds() {
    379       return mSuperBounds;
    380   }
    381 
    382   public ArrayList<TypeInfo> extendsBounds() {
    383       return mExtendsBounds;
    384   }
    385 
    386   public void setIsTypeVariable(boolean b) {
    387     mIsTypeVariable = b;
    388   }
    389 
    390   void setIsWildcard(boolean b) {
    391     mIsWildcard = b;
    392   }
    393 
    394   public boolean isWildcard() {
    395       return mIsWildcard;
    396   }
    397 
    398   public static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
    399     return typeVariables(params, new HashSet<String>());
    400   }
    401 
    402   static HashSet<String> typeVariables(ArrayList<TypeInfo> params, HashSet<String> result) {
    403     if (params != null) {
    404         for (TypeInfo t : params) {
    405             if (t.mIsTypeVariable) {
    406                 result.add(t.mQualifiedTypeName);
    407             }
    408         }
    409     }
    410     return result;
    411   }
    412 
    413 
    414   public boolean isTypeVariable() {
    415     return mIsTypeVariable;
    416   }
    417 
    418   public void resolveTypeVariables(HashSet<String> variables) {
    419     if (mExtendsBounds != null) {
    420       for (TypeInfo bound : mExtendsBounds) {
    421         if (variables.contains(bound.qualifiedTypeName())) {
    422           bound.setIsTypeVariable(true);
    423         }
    424       }
    425     }
    426   }
    427 
    428   public String defaultValue() {
    429     if (mIsPrimitive) {
    430       if ("boolean".equals(mSimpleTypeName)) {
    431         return "false";
    432       } else {
    433         return "0";
    434       }
    435     } else {
    436       return "null";
    437     }
    438   }
    439 
    440   @Override
    441   public String toString() {
    442     String returnString = "";
    443     returnString +=
    444         "Primitive?: " + mIsPrimitive + " TypeVariable?: " + mIsTypeVariable + " Wildcard?: "
    445             + mIsWildcard + " Dimension: " + mDimension + " QualifedTypeName: "
    446             + mQualifiedTypeName;
    447 
    448     if (mTypeArguments != null) {
    449       returnString += "\nTypeArguments: ";
    450       for (TypeInfo tA : mTypeArguments) {
    451         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    452       }
    453     }
    454     if (mSuperBounds != null) {
    455       returnString += "\nSuperBounds: ";
    456       for (TypeInfo tA : mSuperBounds) {
    457         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    458       }
    459     }
    460     if (mExtendsBounds != null) {
    461       returnString += "\nExtendsBounds: ";
    462       for (TypeInfo tA : mExtendsBounds) {
    463         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
    464       }
    465     }
    466     return returnString;
    467   }
    468 
    469   public void addResolution(Resolution resolution) {
    470       if (mResolutions == null) {
    471           mResolutions = new ArrayList<Resolution>();
    472       }
    473 
    474       mResolutions.add(resolution);
    475   }
    476 
    477   public void printResolutions() {
    478       if (mResolutions == null || mResolutions.isEmpty()) {
    479           return;
    480       }
    481 
    482       System.out.println("Resolutions for Type " + mSimpleTypeName + ":");
    483       for (Resolution r : mResolutions) {
    484           System.out.println(r);
    485       }
    486   }
    487 
    488   public boolean resolveResolutions() {
    489       ArrayList<Resolution> resolutions = mResolutions;
    490       mResolutions = new ArrayList<Resolution>();
    491 
    492       boolean allResolved = true;
    493       for (Resolution resolution : resolutions) {
    494           if ("class".equals(resolution.getVariable())) {
    495               StringBuilder qualifiedClassName = new StringBuilder();
    496               InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
    497                       resolution.getInfoBuilder());
    498 
    499               // if we still couldn't resolve it, save it for the next pass
    500               if ("".equals(qualifiedClassName.toString())) {
    501                   mResolutions.add(resolution);
    502                   allResolved = false;
    503               } else {
    504                   mClass = InfoBuilder.Caches.obtainClass(qualifiedClassName.toString());
    505               }
    506           } else if ("variability".equals(resolution.getVariable())) {
    507               StringBuilder qualifiedClassName = new StringBuilder();
    508               for (TypeInfo arg : mTypeArguments) {
    509                 InfoBuilder.resolveQualifiedName(arg.simpleTypeName(), qualifiedClassName,
    510                         resolution.getInfoBuilder());
    511                 arg.setIsTypeVariable(!("".equals(qualifiedClassName.toString())));
    512               }
    513           }
    514       }
    515 
    516       return allResolved;
    517   }
    518 
    519   /**
    520    * Copy this TypeInfo, but replace type arguments with those defined in the
    521    * typeArguments mapping.
    522    * <p>
    523    * If the current type is one of the base types in the mapping (i.e. a parameter itself)
    524    * then this returns the mapped type.
    525    */
    526   public TypeInfo getTypeWithArguments(Map<String, TypeInfo> typeArguments) {
    527     if (typeArguments.containsKey(fullName())) {
    528       return typeArguments.get(fullName());
    529     }
    530 
    531     TypeInfo ti = new TypeInfo(this);
    532     if (typeArguments() != null) {
    533       ArrayList<TypeInfo> newArgs = new ArrayList<TypeInfo>();
    534       for (TypeInfo t : typeArguments()) {
    535         newArgs.add(t.getTypeWithArguments(typeArguments));
    536       }
    537       ti.setTypeArguments(newArgs);
    538     }
    539     return ti;
    540   }
    541 
    542   /**
    543    * Given two TypeInfos that reference the same type, take the first one's type parameters
    544    * and generate a mapping from their names to the type parameters defined in the second.
    545    */
    546   public static Map<String, TypeInfo> getTypeArgumentMapping(TypeInfo generic, TypeInfo typed) {
    547     Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
    548     if (generic != null && generic.typeArguments() != null) {
    549       for (int i = 0; i < generic.typeArguments().size(); i++) {
    550         if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
    551           map.put(generic.typeArguments().get(i).simpleTypeName(), typed.typeArguments().get(i));
    552         }
    553       }
    554     }
    555     return map;
    556   }
    557 
    558   /**
    559    * Given a ClassInfo and a parameterized TypeInfo, take the class's raw type's type parameters
    560    * and generate a mapping from their names to the type parameters defined in the TypeInfo.
    561    */
    562   public static Map<String, TypeInfo> getTypeArgumentMapping(ClassInfo cls, TypeInfo typed) {
    563     return getTypeArgumentMapping(cls.asTypeInfo(), typed);
    564   }
    565 
    566   private ArrayList<Resolution> mResolutions;
    567 
    568   /** Whether the value of {@code mClass} has been resolved. */
    569   private boolean mResolvedClass;
    570 
    571   private boolean mIsPrimitive;
    572   private boolean mIsTypeVariable;
    573   private boolean mIsWildcard;
    574   private String mDimension;
    575   private String mSimpleTypeName;
    576   private String mQualifiedTypeName;
    577   private ClassInfo mClass;
    578   private ArrayList<TypeInfo> mTypeArguments;
    579   private ArrayList<TypeInfo> mSuperBounds;
    580   private ArrayList<TypeInfo> mExtendsBounds;
    581   private String mFullName;
    582 }
    583