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
17package com.google.doclava;
18
19import com.google.clearsilver.jsilver.data.Data;
20
21import java.util.*;
22
23public 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 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   */
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
124  public ClassInfo asClassInfo() {
125    return mClass;
126  }
127
128  public boolean isPrimitive() {
129    return mIsPrimitive;
130  }
131
132  public String dimension() {
133    return mDimension;
134  }
135
136  public void setDimension(String dimension) {
137      mDimension = dimension;
138  }
139
140  public String simpleTypeName() {
141    return mSimpleTypeName;
142  }
143
144  public String qualifiedTypeName() {
145    return mQualifiedTypeName;
146  }
147
148  public String fullName() {
149    if (mFullName != null) {
150      return mFullName;
151    } else {
152      return fullName(new HashSet<String>());
153    }
154  }
155
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
171  public String fullName(HashSet<String> typeVars) {
172    mFullName = fullNameNoDimension(typeVars) + mDimension;
173    return mFullName;
174  }
175
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>>
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
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
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
235  public ArrayList<TypeInfo> typeArguments() {
236    return mTypeArguments;
237  }
238
239  public void makeHDF(Data data, String base) {
240    makeHDFRecursive(data, base, false, false, new HashSet<String>());
241  }
242
243  public void makeQualifiedHDF(Data data, String base) {
244    makeHDFRecursive(data, base, true, false, new HashSet<String>());
245  }
246
247  public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
248    makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
249  }
250
251  public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
252    makeHDFRecursive(data, base, true, false, typeVariables);
253  }
254
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
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
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
308  void setTypeArguments(ArrayList<TypeInfo> args) {
309    mTypeArguments = args;
310  }
311
312  public void addTypeArgument(TypeInfo arg) {
313      if (mTypeArguments == null) {
314          mTypeArguments = new ArrayList<TypeInfo>();
315      }
316
317      mTypeArguments.add(arg);
318  }
319
320  void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
321    mSuperBounds = superBounds;
322    mExtendsBounds = extendsBounds;
323  }
324
325  public ArrayList<TypeInfo> superBounds() {
326      return mSuperBounds;
327  }
328
329  public ArrayList<TypeInfo> extendsBounds() {
330      return mExtendsBounds;
331  }
332
333  void setIsTypeVariable(boolean b) {
334    mIsTypeVariable = b;
335  }
336
337  void setIsWildcard(boolean b) {
338    mIsWildcard = b;
339  }
340
341  public boolean isWildcard() {
342      return mIsWildcard;
343  }
344
345  static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
346    return typeVariables(params, new HashSet<String>());
347  }
348
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
361  public boolean isTypeVariable() {
362    return mIsTypeVariable;
363  }
364
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
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
406  public void addResolution(Resolution resolution) {
407      if (mResolutions == null) {
408          mResolutions = new ArrayList<Resolution>();
409      }
410
411      mResolutions.add(resolution);
412  }
413
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
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   */
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   */
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   */
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