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