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 
TypeInfo(boolean isPrimitive, String dimension, String simpleTypeName, String qualifiedTypeName, ClassInfo cl)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 
TypeInfo(String typeString)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 paramStartPos = typeString.indexOf('<');
45     if (paramStartPos > -1) {
46       ArrayList<TypeInfo> generics = new ArrayList<TypeInfo>();
47       int paramEndPos = typeString.lastIndexOf('>');
48 
49       int entryStartPos = paramStartPos + 1;
50       int bracketNesting = 0;
51       for (int i = entryStartPos; i < paramEndPos; i++) {
52         char c = typeString.charAt(i);
53         if (c == ',' && bracketNesting == 0) {
54           String entry = typeString.substring(entryStartPos, i).trim();
55           TypeInfo info = new TypeInfo(entry);
56           generics.add(info);
57           entryStartPos = i + 1;
58         } else if (c == '<') {
59           bracketNesting++;
60         } else if (c == '>') {
61           bracketNesting--;
62         }
63       }
64 
65       TypeInfo info = new TypeInfo(typeString.substring(entryStartPos, paramEndPos).trim());
66       generics.add(info);
67 
68       mTypeArguments = generics;
69 
70       if (paramEndPos < typeString.length() - 1) {
71         typeString = typeString.substring(0,paramStartPos) + typeString.substring(paramEndPos + 1);
72       } else {
73         typeString = typeString.substring(0,paramStartPos);
74       }
75     }
76 
77     // Dimensions
78     int pos = typeString.indexOf('[');
79     if (pos > -1) {
80       mDimension = typeString.substring(pos);
81       typeString = typeString.substring(0, pos);
82     } else {
83       mDimension = "";
84     }
85 
86     if (PRIMITIVE_TYPES.contains(typeString)) {
87       mIsPrimitive = true;
88       mSimpleTypeName = typeString;
89       mQualifiedTypeName = typeString;
90     } else {
91       mQualifiedTypeName = typeString;
92       pos = typeString.lastIndexOf('.');
93       if (pos > -1) {
94         mSimpleTypeName = typeString.substring(pos + 1);
95       } else {
96         mSimpleTypeName = typeString;
97       }
98     }
99   }
100 
101   /**
102    * Copy Constructor.
103    */
TypeInfo(TypeInfo other)104   private TypeInfo(TypeInfo other) {
105     mIsPrimitive = other.isPrimitive();
106     mIsTypeVariable = other.isTypeVariable();
107     mIsWildcard = other.isWildcard();
108     mDimension = other.dimension();
109     mSimpleTypeName = other.simpleTypeName();
110     mQualifiedTypeName = other.qualifiedTypeName();
111     mClass = other.asClassInfo();
112     if (other.typeArguments() != null) {
113       mTypeArguments = new ArrayList<TypeInfo>(other.typeArguments());
114     }
115     if (other.superBounds() != null) {
116       mSuperBounds = new ArrayList<TypeInfo>(other.superBounds());
117     }
118     if (other.extendsBounds() != null) {
119       mExtendsBounds = new ArrayList<TypeInfo>(other.extendsBounds());
120     }
121     mFullName = other.fullName();
122   }
123 
asClassInfo()124   public ClassInfo asClassInfo() {
125     return mClass;
126   }
127 
isPrimitive()128   public boolean isPrimitive() {
129     return mIsPrimitive;
130   }
131 
dimension()132   public String dimension() {
133     return mDimension;
134   }
135 
setDimension(String dimension)136   public void setDimension(String dimension) {
137       mDimension = dimension;
138   }
139 
simpleTypeName()140   public String simpleTypeName() {
141     return mSimpleTypeName;
142   }
143 
qualifiedTypeName()144   public String qualifiedTypeName() {
145     return mQualifiedTypeName;
146   }
147 
fullName()148   public String fullName() {
149     if (mFullName != null) {
150       return mFullName;
151     } else {
152       return fullName(new HashSet<String>());
153     }
154   }
155 
typeArgumentsName(ArrayList<TypeInfo> args, HashSet<String> typeVars)156   public static String typeArgumentsName(ArrayList<TypeInfo> args, HashSet<String> typeVars) {
157     String result = "<";
158 
159     int i = 0;
160     for (TypeInfo arg : args) {
161       result += arg.fullName(typeVars);
162       if (i != (args.size()-1)) {
163         result += ", ";
164       }
165       i++;
166     }
167     result += ">";
168     return result;
169   }
170 
fullName(HashSet<String> typeVars)171   public String fullName(HashSet<String> typeVars) {
172     mFullName = fullNameNoDimension(typeVars) + mDimension;
173     return mFullName;
174   }
175 
fullNameNoBounds(HashSet<String> typeVars)176   public String fullNameNoBounds(HashSet<String> typeVars) {
177     return fullNameNoDimensionNoBounds(typeVars) + mDimension;
178   }
179 
180   // don't recurse forever with the parameters. This handles
181   // Enum<K extends Enum<K>>
checkRecurringTypeVar(HashSet<String> typeVars)182   private boolean checkRecurringTypeVar(HashSet<String> typeVars) {
183     if (mIsTypeVariable) {
184       if (typeVars.contains(mQualifiedTypeName)) {
185         return true;
186       }
187       typeVars.add(mQualifiedTypeName);
188     }
189     return false;
190   }
191 
fullNameNoDimensionNoBounds(HashSet<String> typeVars)192   private String fullNameNoDimensionNoBounds(HashSet<String> typeVars) {
193     String fullName = null;
194     if (checkRecurringTypeVar(typeVars)) {
195       return mQualifiedTypeName;
196     }
197     /*
198      * if (fullName != null) { return fullName; }
199      */
200     fullName = mQualifiedTypeName;
201     if (mTypeArguments != null && !mTypeArguments.isEmpty()) {
202       fullName += typeArgumentsName(mTypeArguments, typeVars);
203     }
204     return fullName;
205   }
206 
fullNameNoDimension(HashSet<String> typeVars)207   public String fullNameNoDimension(HashSet<String> typeVars) {
208     String fullName = null;
209     if (checkRecurringTypeVar(typeVars)) {
210       return mQualifiedTypeName;
211     }
212     fullName = fullNameNoDimensionNoBounds(typeVars);
213     if (mTypeArguments == null || mTypeArguments.isEmpty()) {
214        if (mSuperBounds != null && !mSuperBounds.isEmpty()) {
215         for (TypeInfo superBound : mSuperBounds) {
216             if (superBound == mSuperBounds.get(0)) {
217                 fullName += " super " + superBound.fullNameNoBounds(typeVars);
218             } else {
219                 fullName += " & " + superBound.fullNameNoBounds(typeVars);
220             }
221         }
222       } else if (mExtendsBounds != null && !mExtendsBounds.isEmpty()) {
223         for (TypeInfo extendsBound : mExtendsBounds) {
224             if (extendsBound == mExtendsBounds.get(0)) {
225                 fullName += " extends " + extendsBound.fullNameNoBounds(typeVars);
226             } else {
227                 fullName += " & " + extendsBound.fullNameNoBounds(typeVars);
228             }
229         }
230       }
231     }
232     return fullName;
233   }
234 
typeArguments()235   public ArrayList<TypeInfo> typeArguments() {
236     return mTypeArguments;
237   }
238 
makeHDF(Data data, String base)239   public void makeHDF(Data data, String base) {
240     makeHDFRecursive(data, base, false, false, new HashSet<String>());
241   }
242 
makeQualifiedHDF(Data data, String base)243   public void makeQualifiedHDF(Data data, String base) {
244     makeHDFRecursive(data, base, true, false, new HashSet<String>());
245   }
246 
makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables)247   public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
248     makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
249   }
250 
makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables)251   public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
252     makeHDFRecursive(data, base, true, false, typeVariables);
253   }
254 
makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg, HashSet<String> typeVars)255   private void makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg,
256       HashSet<String> typeVars) {
257     String label = qualified ? qualifiedTypeName() : simpleTypeName();
258     label += (isLastVararg) ? "..." : dimension();
259     data.setValue(base + ".label", label);
260     if (mIsTypeVariable || mIsWildcard) {
261       // could link to an @param tag on the class to describe this
262       // but for now, just don't make it a link
263     } else if (!isPrimitive() && mClass != null) {
264       if (mClass.isIncluded()) {
265         data.setValue(base + ".link", mClass.htmlPage());
266         data.setValue(base + ".since", mClass.getSince());
267       } else {
268         Doclava.federationTagger.tag(mClass);
269         if (!mClass.getFederatedReferences().isEmpty()) {
270           FederatedSite site = mClass.getFederatedReferences().iterator().next();
271           data.setValue(base + ".link", site.linkFor(mClass.htmlPage()));
272           data.setValue(base + ".federated", site.name());
273         }
274       }
275     }
276 
277     if (mIsTypeVariable) {
278       if (typeVars.contains(qualifiedTypeName())) {
279         // don't recurse forever with the parameters. This handles
280         // Enum<K extends Enum<K>>
281         return;
282       }
283       typeVars.add(qualifiedTypeName());
284     }
285     if (mTypeArguments != null) {
286       TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars);
287     }
288     if (mSuperBounds != null) {
289       TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars);
290     }
291     if (mExtendsBounds != null) {
292       TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars);
293     }
294   }
295 
makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified, HashSet<String> typeVariables)296   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified,
297       HashSet<String> typeVariables) {
298     int i = 0;
299     for (TypeInfo type : types) {
300       type.makeHDFRecursive(data, base + "." + i++, qualified, false, typeVariables);
301     }
302   }
303 
makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified)304   public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified) {
305     makeHDF(data, base, types, qualified, new HashSet<String>());
306   }
307 
setTypeArguments(ArrayList<TypeInfo> args)308   void setTypeArguments(ArrayList<TypeInfo> args) {
309     mTypeArguments = args;
310   }
311 
addTypeArgument(TypeInfo arg)312   public void addTypeArgument(TypeInfo arg) {
313       if (mTypeArguments == null) {
314           mTypeArguments = new ArrayList<TypeInfo>();
315       }
316 
317       mTypeArguments.add(arg);
318   }
319 
setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds)320   void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
321     mSuperBounds = superBounds;
322     mExtendsBounds = extendsBounds;
323   }
324 
superBounds()325   public ArrayList<TypeInfo> superBounds() {
326       return mSuperBounds;
327   }
328 
extendsBounds()329   public ArrayList<TypeInfo> extendsBounds() {
330       return mExtendsBounds;
331   }
332 
setIsTypeVariable(boolean b)333   void setIsTypeVariable(boolean b) {
334     mIsTypeVariable = b;
335   }
336 
setIsWildcard(boolean b)337   void setIsWildcard(boolean b) {
338     mIsWildcard = b;
339   }
340 
isWildcard()341   public boolean isWildcard() {
342       return mIsWildcard;
343   }
344 
typeVariables(ArrayList<TypeInfo> params)345   static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
346     return typeVariables(params, new HashSet<String>());
347   }
348 
typeVariables(ArrayList<TypeInfo> params, HashSet<String> result)349   static HashSet<String> typeVariables(ArrayList<TypeInfo> params, HashSet<String> result) {
350     if (params != null) {
351         for (TypeInfo t : params) {
352             if (t.mIsTypeVariable) {
353                 result.add(t.mQualifiedTypeName);
354             }
355         }
356     }
357     return result;
358   }
359 
360 
isTypeVariable()361   public boolean isTypeVariable() {
362     return mIsTypeVariable;
363   }
364 
defaultValue()365   public String defaultValue() {
366     if (mIsPrimitive) {
367       if ("boolean".equals(mSimpleTypeName)) {
368         return "false";
369       } else {
370         return "0";
371       }
372     } else {
373       return "null";
374     }
375   }
376 
377   @Override
toString()378   public String toString() {
379     String returnString = "";
380     returnString +=
381         "Primitive?: " + mIsPrimitive + " TypeVariable?: " + mIsTypeVariable + " Wildcard?: "
382             + mIsWildcard + " Dimension: " + mDimension + " QualifedTypeName: "
383             + mQualifiedTypeName;
384 
385     if (mTypeArguments != null) {
386       returnString += "\nTypeArguments: ";
387       for (TypeInfo tA : mTypeArguments) {
388         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
389       }
390     }
391     if (mSuperBounds != null) {
392       returnString += "\nSuperBounds: ";
393       for (TypeInfo tA : mSuperBounds) {
394         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
395       }
396     }
397     if (mExtendsBounds != null) {
398       returnString += "\nExtendsBounds: ";
399       for (TypeInfo tA : mExtendsBounds) {
400         returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
401       }
402     }
403     return returnString;
404   }
405 
addResolution(Resolution resolution)406   public void addResolution(Resolution resolution) {
407       if (mResolutions == null) {
408           mResolutions = new ArrayList<Resolution>();
409       }
410 
411       mResolutions.add(resolution);
412   }
413 
printResolutions()414   public void printResolutions() {
415       if (mResolutions == null || mResolutions.isEmpty()) {
416           return;
417       }
418 
419       System.out.println("Resolutions for Type " + mSimpleTypeName + ":");
420       for (Resolution r : mResolutions) {
421           System.out.println(r);
422       }
423   }
424 
resolveResolutions()425   public boolean resolveResolutions() {
426       ArrayList<Resolution> resolutions = mResolutions;
427       mResolutions = new ArrayList<Resolution>();
428 
429       boolean allResolved = true;
430       for (Resolution resolution : resolutions) {
431           if ("class".equals(resolution.getVariable())) {
432               StringBuilder qualifiedClassName = new StringBuilder();
433               InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
434                       resolution.getInfoBuilder());
435 
436               // if we still couldn't resolve it, save it for the next pass
437               if ("".equals(qualifiedClassName.toString())) {
438                   mResolutions.add(resolution);
439                   allResolved = false;
440               } else {
441                   mClass = InfoBuilder.Caches.obtainClass(qualifiedClassName.toString());
442               }
443           }
444       }
445 
446       return allResolved;
447   }
448 
449   /**
450    * Copy this TypeInfo, but replace type arguments with those defined in the
451    * typeArguments mapping.
452    * <p>
453    * If the current type is one of the base types in the mapping (i.e. a parameter itself)
454    * then this returns the mapped type.
455    */
getTypeWithArguments(Map<String, TypeInfo> typeArguments)456   public TypeInfo getTypeWithArguments(Map<String, TypeInfo> typeArguments) {
457     if (typeArguments.containsKey(fullName())) {
458       return typeArguments.get(fullName());
459     }
460 
461     TypeInfo ti = new TypeInfo(this);
462     if (typeArguments() != null) {
463       ArrayList<TypeInfo> newArgs = new ArrayList<TypeInfo>();
464       for (TypeInfo t : typeArguments()) {
465         newArgs.add(t.getTypeWithArguments(typeArguments));
466       }
467       ti.setTypeArguments(newArgs);
468     }
469     return ti;
470   }
471 
472   /**
473    * Given two TypeInfos that reference the same type, take the first one's type parameters
474    * and generate a mapping from their names to the type parameters defined in the second.
475    */
getTypeArgumentMapping(TypeInfo generic, TypeInfo typed)476   public static Map<String, TypeInfo> getTypeArgumentMapping(TypeInfo generic, TypeInfo typed) {
477     Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
478     for (int i = 0; i < generic.typeArguments().size(); i++) {
479       if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
480         map.put(generic.typeArguments().get(i).fullName(), typed.typeArguments().get(i));
481       }
482     }
483     return map;
484   }
485 
486   /**
487    * Given a ClassInfo and a parameterized TypeInfo, take the class's raw type's type parameters
488    * and generate a mapping from their names to the type parameters defined in the TypeInfo.
489    */
getTypeArgumentMapping(ClassInfo cls, TypeInfo typed)490   public static Map<String, TypeInfo> getTypeArgumentMapping(ClassInfo cls, TypeInfo typed) {
491     return getTypeArgumentMapping(cls.asTypeInfo(), typed);
492   }
493 
494   private ArrayList<Resolution> mResolutions;
495 
496   private boolean mIsPrimitive;
497   private boolean mIsTypeVariable;
498   private boolean mIsWildcard;
499   private String mDimension;
500   private String mSimpleTypeName;
501   private String mQualifiedTypeName;
502   private ClassInfo mClass;
503   private ArrayList<TypeInfo> mTypeArguments;
504   private ArrayList<TypeInfo> mSuperBounds;
505   private ArrayList<TypeInfo> mExtendsBounds;
506   private String mFullName;
507 }
508