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  public ClassInfo asClassInfo() {
102    return mClass;
103  }
104
105  public boolean isPrimitive() {
106    return mIsPrimitive;
107  }
108
109  public String dimension() {
110    return mDimension;
111  }
112
113  public void setDimension(String dimension) {
114      mDimension = dimension;
115  }
116
117  public String simpleTypeName() {
118    return mSimpleTypeName;
119  }
120
121  public String qualifiedTypeName() {
122    return mQualifiedTypeName;
123  }
124
125  public String fullName() {
126    if (mFullName != null) {
127      return mFullName;
128    } else {
129      return fullName(new HashSet<String>());
130    }
131  }
132
133  public static String typeArgumentsName(ArrayList<TypeInfo> args, HashSet<String> typeVars) {
134    String result = "<";
135
136    int i = 0;
137    for (TypeInfo arg : args) {
138      result += arg.fullName(typeVars);
139      if (i != (args.size()-1)) {
140        result += ", ";
141      }
142      i++;
143    }
144    result += ">";
145    return result;
146  }
147
148  public String fullName(HashSet<String> typeVars) {
149    mFullName = fullNameNoDimension(typeVars) + mDimension;
150    return mFullName;
151  }
152
153  public String fullNameNoDimension(HashSet<String> typeVars) {
154    String fullName = null;
155    if (mIsTypeVariable) {
156      if (typeVars.contains(mQualifiedTypeName)) {
157        // don't recurse forever with the parameters. This handles
158        // Enum<K extends Enum<K>>
159        return mQualifiedTypeName;
160      }
161      typeVars.add(mQualifiedTypeName);
162    }
163    /*
164     * if (fullName != null) { return fullName; }
165     */
166    fullName = mQualifiedTypeName;
167    if (mTypeArguments != null && !mTypeArguments.isEmpty()) {
168      fullName += typeArgumentsName(mTypeArguments, typeVars);
169    } else if (mSuperBounds != null && !mSuperBounds.isEmpty()) {
170        for (TypeInfo superBound : mSuperBounds) {
171            if (superBound == mSuperBounds.get(0)) {
172                fullName += " super " + superBound.fullName(typeVars);
173            } else {
174                fullName += " & " + superBound.fullName(typeVars);
175            }
176        }
177    } else if (mExtendsBounds != null && !mExtendsBounds.isEmpty()) {
178        for (TypeInfo extendsBound : mExtendsBounds) {
179            if (extendsBound == mExtendsBounds.get(0)) {
180                fullName += " extends " + extendsBound.fullName(typeVars);
181            } else {
182                fullName += " & " + extendsBound.fullName(typeVars);
183            }
184        }
185    }
186    return fullName;
187  }
188
189  public ArrayList<TypeInfo> typeArguments() {
190    return mTypeArguments;
191  }
192
193  public void makeHDF(Data data, String base) {
194    makeHDFRecursive(data, base, false, false, new HashSet<String>());
195  }
196
197  public void makeQualifiedHDF(Data data, String base) {
198    makeHDFRecursive(data, base, true, false, new HashSet<String>());
199  }
200
201  public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) {
202    makeHDFRecursive(data, base, false, isLastVararg, typeVariables);
203  }
204
205  public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) {
206    makeHDFRecursive(data, base, true, false, typeVariables);
207  }
208
209  private void makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg,
210      HashSet<String> typeVars) {
211    String label = qualified ? qualifiedTypeName() : simpleTypeName();
212    label += (isLastVararg) ? "..." : dimension();
213    data.setValue(base + ".label", label);
214    if (mIsTypeVariable || mIsWildcard) {
215      // could link to an @param tag on the class to describe this
216      // but for now, just don't make it a link
217    } else if (!isPrimitive() && mClass != null) {
218      if (mClass.isIncluded()) {
219        data.setValue(base + ".link", mClass.htmlPage());
220        data.setValue(base + ".since", mClass.getSince());
221      } else {
222        Doclava.federationTagger.tag(mClass);
223        if (!mClass.getFederatedReferences().isEmpty()) {
224          FederatedSite site = mClass.getFederatedReferences().iterator().next();
225          data.setValue(base + ".link", site.linkFor(mClass.htmlPage()));
226          data.setValue(base + ".federated", site.name());
227        }
228      }
229    }
230
231    if (mIsTypeVariable) {
232      if (typeVars.contains(qualifiedTypeName())) {
233        // don't recurse forever with the parameters. This handles
234        // Enum<K extends Enum<K>>
235        return;
236      }
237      typeVars.add(qualifiedTypeName());
238    }
239    if (mTypeArguments != null) {
240      TypeInfo.makeHDF(data, base + ".typeArguments", mTypeArguments, qualified, typeVars);
241    }
242    if (mSuperBounds != null) {
243      TypeInfo.makeHDF(data, base + ".superBounds", mSuperBounds, qualified, typeVars);
244    }
245    if (mExtendsBounds != null) {
246      TypeInfo.makeHDF(data, base + ".extendsBounds", mExtendsBounds, qualified, typeVars);
247    }
248  }
249
250  public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified,
251      HashSet<String> typeVariables) {
252    int i = 0;
253    for (TypeInfo type : types) {
254      type.makeHDFRecursive(data, base + "." + i++, qualified, false, typeVariables);
255    }
256  }
257
258  public static void makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified) {
259    makeHDF(data, base, types, qualified, new HashSet<String>());
260  }
261
262  void setTypeArguments(ArrayList<TypeInfo> args) {
263    mTypeArguments = args;
264  }
265
266  public void addTypeArgument(TypeInfo arg) {
267      if (mTypeArguments == null) {
268          mTypeArguments = new ArrayList<TypeInfo>();
269      }
270
271      mTypeArguments.add(arg);
272  }
273
274  void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
275    mSuperBounds = superBounds;
276    mExtendsBounds = extendsBounds;
277  }
278
279  public ArrayList<TypeInfo> superBounds() {
280      return mSuperBounds;
281  }
282
283  public ArrayList<TypeInfo> extendsBounds() {
284      return mExtendsBounds;
285  }
286
287  void setIsTypeVariable(boolean b) {
288    mIsTypeVariable = b;
289  }
290
291  void setIsWildcard(boolean b) {
292    mIsWildcard = b;
293  }
294
295  public boolean isWildcard() {
296      return mIsWildcard;
297  }
298
299  static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
300    return typeVariables(params, new HashSet<String>());
301  }
302
303  static HashSet<String> typeVariables(ArrayList<TypeInfo> params, HashSet<String> result) {
304    if (params != null) {
305        for (TypeInfo t : params) {
306            if (t.mIsTypeVariable) {
307                result.add(t.mQualifiedTypeName);
308            }
309        }
310    }
311    return result;
312  }
313
314
315  public boolean isTypeVariable() {
316    return mIsTypeVariable;
317  }
318
319  public String defaultValue() {
320    if (mIsPrimitive) {
321      if ("boolean".equals(mSimpleTypeName)) {
322        return "false";
323      } else {
324        return "0";
325      }
326    } else {
327      return "null";
328    }
329  }
330
331  @Override
332  public String toString() {
333    String returnString = "";
334    returnString +=
335        "Primitive?: " + mIsPrimitive + " TypeVariable?: " + mIsTypeVariable + " Wildcard?: "
336            + mIsWildcard + " Dimension: " + mDimension + " QualifedTypeName: "
337            + mQualifiedTypeName;
338
339    if (mTypeArguments != null) {
340      returnString += "\nTypeArguments: ";
341      for (TypeInfo tA : mTypeArguments) {
342        returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
343      }
344    }
345    if (mSuperBounds != null) {
346      returnString += "\nSuperBounds: ";
347      for (TypeInfo tA : mSuperBounds) {
348        returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
349      }
350    }
351    if (mExtendsBounds != null) {
352      returnString += "\nExtendsBounds: ";
353      for (TypeInfo tA : mExtendsBounds) {
354        returnString += tA.qualifiedTypeName() + "(" + tA + ") ";
355      }
356    }
357    return returnString;
358  }
359
360  public void addResolution(Resolution resolution) {
361      if (mResolutions == null) {
362          mResolutions = new ArrayList<Resolution>();
363      }
364
365      mResolutions.add(resolution);
366  }
367
368  public void printResolutions() {
369      if (mResolutions == null || mResolutions.isEmpty()) {
370          return;
371      }
372
373      System.out.println("Resolutions for Type " + mSimpleTypeName + ":");
374      for (Resolution r : mResolutions) {
375          System.out.println(r);
376      }
377  }
378
379  public boolean resolveResolutions() {
380      ArrayList<Resolution> resolutions = mResolutions;
381      mResolutions = new ArrayList<Resolution>();
382
383      boolean allResolved = true;
384      for (Resolution resolution : resolutions) {
385          if ("class".equals(resolution.getVariable())) {
386              StringBuilder qualifiedClassName = new StringBuilder();
387              InfoBuilder.resolveQualifiedName(resolution.getValue(), qualifiedClassName,
388                      resolution.getInfoBuilder());
389
390              // if we still couldn't resolve it, save it for the next pass
391              if ("".equals(qualifiedClassName.toString())) {
392                  mResolutions.add(resolution);
393                  allResolved = false;
394              } else {
395                  mClass = InfoBuilder.Caches.obtainClass(qualifiedClassName.toString());
396              }
397          }
398      }
399
400      return allResolved;
401  }
402
403  private ArrayList<Resolution> mResolutions;
404
405  private boolean mIsPrimitive;
406  private boolean mIsTypeVariable;
407  private boolean mIsWildcard;
408  private String mDimension;
409  private String mSimpleTypeName;
410  private String mQualifiedTypeName;
411  private ClassInfo mClass;
412  private ArrayList<TypeInfo> mTypeArguments;
413  private ArrayList<TypeInfo> mSuperBounds;
414  private ArrayList<TypeInfo> mExtendsBounds;
415  private String mFullName;
416}
417