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 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 */ TypeInfo(TypeInfo other)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 */ asClassInfo()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 isPrimitive()170 public boolean isPrimitive() { 171 return mIsPrimitive; 172 } 173 dimension()174 public String dimension() { 175 return mDimension; 176 } 177 setDimension(String dimension)178 public void setDimension(String dimension) { 179 mDimension = dimension; 180 } 181 simpleTypeName()182 public String simpleTypeName() { 183 return mSimpleTypeName; 184 } 185 qualifiedTypeName()186 public String qualifiedTypeName() { 187 return mQualifiedTypeName; 188 } 189 fullName()190 public String fullName() { 191 if (mFullName != null) { 192 return mFullName; 193 } else { 194 return fullName(new HashSet<String>()); 195 } 196 } 197 typeArgumentsName(ArrayList<TypeInfo> args, HashSet<String> typeVars)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 fullName(HashSet<String> typeVars)213 public String fullName(HashSet<String> typeVars) { 214 mFullName = fullNameNoDimension(typeVars) + mDimension; 215 return mFullName; 216 } 217 fullNameNoBounds(HashSet<String> typeVars)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>> checkRecurringTypeVar(HashSet<String> typeVars)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 fullNameNoDimensionNoBounds(HashSet<String> typeVars)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 fullNameNoDimension(HashSet<String> typeVars)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 dexName()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 typeArguments()288 public ArrayList<TypeInfo> typeArguments() { 289 return mTypeArguments; 290 } 291 makeHDF(Data data, String base)292 public void makeHDF(Data data, String base) { 293 makeHDFRecursive(data, base, false, false, new HashSet<String>()); 294 } 295 makeQualifiedHDF(Data data, String base)296 public void makeQualifiedHDF(Data data, String base) { 297 makeHDFRecursive(data, base, true, false, new HashSet<String>()); 298 } 299 makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables)300 public void makeHDF(Data data, String base, boolean isLastVararg, HashSet<String> typeVariables) { 301 makeHDFRecursive(data, base, false, isLastVararg, typeVariables); 302 } 303 makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables)304 public void makeQualifiedHDF(Data data, String base, HashSet<String> typeVariables) { 305 makeHDFRecursive(data, base, true, false, typeVariables); 306 } 307 makeHDFRecursive(Data data, String base, boolean qualified, boolean isLastVararg, HashSet<String> typeVars)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 makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified, HashSet<String> typeVariables)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 makeHDF(Data data, String base, ArrayList<TypeInfo> types, boolean qualified)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 setTypeArguments(ArrayList<TypeInfo> args)361 void setTypeArguments(ArrayList<TypeInfo> args) { 362 mTypeArguments = args; 363 } 364 addTypeArgument(TypeInfo arg)365 public void addTypeArgument(TypeInfo arg) { 366 if (mTypeArguments == null) { 367 mTypeArguments = new ArrayList<TypeInfo>(); 368 } 369 370 mTypeArguments.add(arg); 371 } 372 setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds)373 public void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) { 374 mSuperBounds = superBounds; 375 mExtendsBounds = extendsBounds; 376 } 377 superBounds()378 public ArrayList<TypeInfo> superBounds() { 379 return mSuperBounds; 380 } 381 extendsBounds()382 public ArrayList<TypeInfo> extendsBounds() { 383 return mExtendsBounds; 384 } 385 setIsTypeVariable(boolean b)386 public void setIsTypeVariable(boolean b) { 387 mIsTypeVariable = b; 388 } 389 setIsWildcard(boolean b)390 void setIsWildcard(boolean b) { 391 mIsWildcard = b; 392 } 393 isWildcard()394 public boolean isWildcard() { 395 return mIsWildcard; 396 } 397 typeVariables(ArrayList<TypeInfo> params)398 public static HashSet<String> typeVariables(ArrayList<TypeInfo> params) { 399 return typeVariables(params, new HashSet<String>()); 400 } 401 typeVariables(ArrayList<TypeInfo> params, HashSet<String> result)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 isTypeVariable()414 public boolean isTypeVariable() { 415 return mIsTypeVariable; 416 } 417 resolveTypeVariables(HashSet<String> variables)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 defaultValue()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 toString()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 addResolution(Resolution resolution)469 public void addResolution(Resolution resolution) { 470 if (mResolutions == null) { 471 mResolutions = new ArrayList<Resolution>(); 472 } 473 474 mResolutions.add(resolution); 475 } 476 printResolutions()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 resolveResolutions()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 */ getTypeWithArguments(Map<String, TypeInfo> typeArguments)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 */ getTypeArgumentMapping(TypeInfo generic, TypeInfo typed)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 */ getTypeArgumentMapping(ClassInfo cls, TypeInfo typed)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