1 /* 2 * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.javadoc.main; 27 28 import java.io.File; 29 import java.util.Locale; 30 31 import com.sun.javadoc.*; 32 import com.sun.tools.javac.code.Printer; 33 import com.sun.tools.javac.code.Symbol; 34 import com.sun.tools.javac.code.Type.CapturedType; 35 import com.sun.tools.javac.util.*; 36 37 import static com.sun.tools.javac.code.Kinds.Kind.*; 38 39 /** 40 * Represents a see also documentation tag. 41 * The @see tag can be plain text, or reference a class or member. 42 * 43 * <p><b>This is NOT part of any supported API. 44 * If you write code that depends on this, you do so at your own risk. 45 * This code and its internal interfaces are subject to change or 46 * deletion without notice.</b> 47 * 48 * @author Kaiyang Liu (original) 49 * @author Robert Field (rewrite) 50 * @author Atul M Dambalkar 51 * 52 */ 53 @Deprecated(since="9", forRemoval=true) 54 @SuppressWarnings("removal") 55 class SeeTagImpl extends TagImpl implements SeeTag, LayoutCharacters { 56 57 //### TODO: Searching for classes, fields, and methods 58 //### should follow the normal rules applied by the compiler. 59 60 /** 61 * where of where#what - i.e. the class name (may be empty) 62 */ 63 private String where; 64 65 /** 66 * what of where#what - i.e. the member (may be null) 67 */ 68 private String what; 69 70 private PackageDoc referencedPackage; 71 private ClassDoc referencedClass; 72 private MemberDoc referencedMember; 73 74 String label = ""; 75 SeeTagImpl(DocImpl holder, String name, String text)76 SeeTagImpl(DocImpl holder, String name, String text) { 77 super(holder, name, text); 78 parseSeeString(); 79 if (where != null) { 80 ClassDocImpl container = null; 81 if (holder instanceof MemberDoc) { 82 container = 83 (ClassDocImpl)((ProgramElementDoc)holder).containingClass(); 84 } else if (holder instanceof ClassDoc) { 85 container = (ClassDocImpl)holder; 86 } 87 findReferenced(container); 88 if (showRef) showRef(); 89 } 90 } 91 92 private static final boolean showRef = false; 93 showRef()94 private void showRef() { 95 Symbol sym; 96 if (referencedMember != null) { 97 if (referencedMember instanceof MethodDocImpl) 98 sym = ((MethodDocImpl) referencedMember).sym; 99 else if (referencedMember instanceof FieldDocImpl) 100 sym = ((FieldDocImpl) referencedMember).sym; 101 else 102 sym = ((ConstructorDocImpl) referencedMember).sym; 103 } else if (referencedClass != null) { 104 sym = ((ClassDocImpl) referencedClass).tsym; 105 } else if (referencedPackage != null) { 106 sym = ((PackageDocImpl) referencedPackage).sym; 107 } else 108 return; 109 110 final JavacMessages messages = JavacMessages.instance(docenv().context); 111 Locale locale = Locale.getDefault(); 112 Printer printer = new Printer() { 113 int count; 114 @Override 115 protected String localize(Locale locale, String key, Object... args) { 116 return messages.getLocalizedString(locale, key, args); 117 } 118 @Override 119 protected String capturedVarId(CapturedType t, Locale locale) { 120 return "CAP#" + (++count); 121 } 122 }; 123 124 String s = text.replaceAll("\\s+", " "); // normalize white space 125 int sp = s.indexOf(" "); 126 int lparen = s.indexOf("("); 127 int rparen = s.indexOf(")"); 128 String seetext = (sp == -1) ? s 129 : (lparen == -1 || sp < lparen) ? s.substring(0, sp) 130 : s.substring(0, rparen + 1); 131 132 File file = new File(holder.position().file().getAbsoluteFile().toURI().normalize()); 133 134 StringBuilder sb = new StringBuilder(); 135 sb.append("+++ ").append(file).append(": ") 136 .append(name()).append(" ").append(seetext).append(": "); 137 sb.append(sym.getKind()).append(" "); 138 if (sym.kind == MTH || sym.kind == VAR) 139 sb.append(printer.visit(sym.owner, locale)).append("."); 140 sb.append(printer.visit(sym, locale)); 141 142 System.err.println(sb); 143 } 144 145 /** 146 * get the class name part of @see, For instance, 147 * if the comment is @see String#startsWith(java.lang.String) . 148 * This function returns String. 149 * Returns null if format was not that of java reference. 150 * Return empty string if class name was not specified.. 151 */ referencedClassName()152 public String referencedClassName() { 153 return where; 154 } 155 156 /** 157 * get the package referenced by @see. For instance, 158 * if the comment is @see java.lang 159 * This function returns a PackageDocImpl for java.lang 160 * Returns null if no known package found. 161 */ referencedPackage()162 public PackageDoc referencedPackage() { 163 return referencedPackage; 164 } 165 166 /** 167 * get the class referenced by the class name part of @see, For instance, 168 * if the comment is @see String#startsWith(java.lang.String) . 169 * This function returns a ClassDocImpl for java.lang.String. 170 * Returns null if class is not a class specified on the javadoc command line.. 171 */ referencedClass()172 public ClassDoc referencedClass() { 173 return referencedClass; 174 } 175 176 /** 177 * get the name of the member referenced by the prototype part of @see, 178 * For instance, 179 * if the comment is @see String#startsWith(java.lang.String) . 180 * This function returns "startsWith(java.lang.String)" 181 * Returns null if format was not that of java reference. 182 * Return empty string if member name was not specified.. 183 */ referencedMemberName()184 public String referencedMemberName() { 185 return what; 186 } 187 188 /** 189 * get the member referenced by the prototype part of @see, 190 * For instance, 191 * if the comment is @see String#startsWith(java.lang.String) . 192 * This function returns a MethodDocImpl for startsWith. 193 * Returns null if member could not be determined. 194 */ referencedMember()195 public MemberDoc referencedMember() { 196 return referencedMember; 197 } 198 199 200 /** 201 * parse @see part of comment. Determine 'where' and 'what' 202 */ parseSeeString()203 private void parseSeeString() { 204 int len = text.length(); 205 if (len == 0) { 206 return; 207 } 208 switch (text.charAt(0)) { 209 case '<': 210 if (text.charAt(len-1) != '>') { 211 docenv().warning(holder, 212 "tag.see.no_close_bracket_on_url", 213 name, text); 214 } 215 return; 216 case '"': 217 if (len == 1 || text.charAt(len-1) != '"') { 218 docenv().warning(holder, 219 "tag.see.no_close_quote", 220 name, text); 221 } else { 222 // text = text.substring(1,len-1); // strip quotes 223 } 224 return; 225 } 226 227 // check that the text is one word, with possible parentheses 228 // this part of code doesn't allow 229 // @see <a href=.....>asfd</a> 230 // comment it. 231 232 // the code assumes that there is no initial white space. 233 int parens = 0; 234 int commentstart = 0; 235 int start = 0; 236 int cp; 237 for (int i = start; i < len ; i += Character.charCount(cp)) { 238 cp = text.codePointAt(i); 239 switch (cp) { 240 case '(': parens++; break; 241 case ')': parens--; break; 242 case '[': case ']': case '.': case '#': break; 243 case ',': 244 if (parens <= 0) { 245 docenv().warning(holder, 246 "tag.see.malformed_see_tag", 247 name, text); 248 return; 249 } 250 break; 251 case ' ': case '\t': case '\n': case CR: 252 if (parens == 0) { //here onwards the comment starts. 253 commentstart = i; 254 i = len; 255 } 256 break; 257 default: 258 if (!Character.isJavaIdentifierPart(cp)) { 259 docenv().warning(holder, 260 "tag.see.illegal_character", 261 name, ""+cp, text); 262 } 263 break; 264 } 265 } 266 if (parens != 0) { 267 docenv().warning(holder, 268 "tag.see.malformed_see_tag", 269 name, text); 270 return; 271 } 272 273 String seetext = ""; 274 String labeltext = ""; 275 276 if (commentstart > 0) { 277 seetext = text.substring(start, commentstart); 278 labeltext = text.substring(commentstart + 1); 279 // strip off the white space which can be between seetext and the 280 // actual label. 281 for (int i = 0; i < labeltext.length(); i++) { 282 char ch2 = labeltext.charAt(i); 283 if (!(ch2 == ' ' || ch2 == '\t' || ch2 == '\n')) { 284 label = labeltext.substring(i); 285 break; 286 } 287 } 288 } else { 289 seetext = text; 290 label = ""; 291 } 292 293 int sharp = seetext.indexOf('#'); 294 if (sharp >= 0) { 295 // class#member 296 where = seetext.substring(0, sharp); 297 what = seetext.substring(sharp + 1); 298 } else { 299 if (seetext.indexOf('(') >= 0) { 300 docenv().warning(holder, 301 "tag.see.missing_sharp", 302 name, text); 303 where = ""; 304 what = seetext; 305 } 306 else { 307 // no member specified, text names class 308 where = seetext; 309 what = null; 310 } 311 } 312 } 313 314 /** 315 * Find what is referenced by the see also. If possible, sets 316 * referencedClass and referencedMember. 317 * 318 * @param containingClass the class containing the comment containing 319 * the tag. May be null, if, for example, it is a package comment. 320 */ findReferenced(ClassDocImpl containingClass)321 private void findReferenced(ClassDocImpl containingClass) { 322 if (where.length() > 0) { 323 if (containingClass != null) { 324 referencedClass = containingClass.findClass(where); 325 } else { 326 referencedClass = docenv().lookupClass(where); 327 } 328 if (referencedClass == null && holder() instanceof ProgramElementDoc) { 329 referencedClass = docenv().lookupClass( 330 ((ProgramElementDoc) holder()).containingPackage().name() + "." + where); 331 } 332 333 if (referencedClass == null) { /* may just not be in this run */ 334 // check if it's a package name 335 referencedPackage = docenv().lookupPackage(where); 336 return; 337 } 338 } else { 339 if (containingClass == null) { 340 docenv().warning(holder, 341 "tag.see.class_not_specified", 342 name, text); 343 return; 344 } else { 345 referencedClass = containingClass; 346 } 347 } 348 where = referencedClass.qualifiedName(); 349 350 if (what == null) { 351 return; 352 } else { 353 int paren = what.indexOf('('); 354 String memName = (paren >= 0 ? what.substring(0, paren) : what); 355 String[] paramarr; 356 if (paren > 0) { 357 // has parameter list -- should be method or constructor 358 paramarr = new ParameterParseMachine(what. 359 substring(paren, what.length())).parseParameters(); 360 if (paramarr != null) { 361 referencedMember = findExecutableMember(memName, paramarr, 362 referencedClass); 363 } else { 364 referencedMember = null; 365 } 366 } else { 367 // no parameter list -- should be field 368 referencedMember = findExecutableMember(memName, null, 369 referencedClass); 370 FieldDoc fd = ((ClassDocImpl)referencedClass). 371 findField(memName); 372 // when no args given, prefer fields over methods 373 if (referencedMember == null || 374 (fd != null && 375 fd.containingClass() 376 .subclassOf(referencedMember.containingClass()))) { 377 referencedMember = fd; 378 } 379 } 380 if (referencedMember == null) { 381 docenv().warning(holder, 382 "tag.see.can_not_find_member", 383 name, what, where); 384 } 385 } 386 } 387 findReferencedMethod(String memName, String[] paramarr, ClassDoc referencedClass)388 private MemberDoc findReferencedMethod(String memName, String[] paramarr, 389 ClassDoc referencedClass) { 390 MemberDoc meth = findExecutableMember(memName, paramarr, referencedClass); 391 if (meth == null) { 392 for (ClassDoc nestedClass : referencedClass.innerClasses()) { 393 meth = findReferencedMethod(memName, paramarr, nestedClass); 394 if (meth != null) { 395 return meth; 396 } 397 } 398 } 399 return null; 400 } 401 findExecutableMember(String memName, String[] paramarr, ClassDoc referencedClass)402 private MemberDoc findExecutableMember(String memName, String[] paramarr, 403 ClassDoc referencedClass) { 404 String className = referencedClass.name(); 405 if (memName.equals(className.substring(className.lastIndexOf(".") + 1))) { 406 return ((ClassDocImpl)referencedClass).findConstructor(memName, 407 paramarr); 408 } else { // it's a method. 409 return ((ClassDocImpl)referencedClass).findMethod(memName, 410 paramarr); 411 } 412 } 413 414 // separate "int, String" from "(int, String)" 415 // (int i, String s) ==> [0] = "int", [1] = String 416 // (int[][], String[]) ==> [0] = "int[][]" // [1] = "String[]" 417 class ParameterParseMachine { 418 static final int START = 0; 419 static final int TYPE = 1; 420 static final int NAME = 2; 421 static final int TNSPACE = 3; // space between type and name 422 static final int ARRAYDECORATION = 4; 423 static final int ARRAYSPACE = 5; 424 425 String parameters; 426 427 StringBuilder typeId; 428 429 ListBuffer<String> paramList; 430 ParameterParseMachine(String parameters)431 ParameterParseMachine(String parameters) { 432 this.parameters = parameters; 433 this.paramList = new ListBuffer<>(); 434 typeId = new StringBuilder(); 435 } 436 parseParameters()437 public String[] parseParameters() { 438 if (parameters.equals("()")) { 439 return new String[0]; 440 } // now strip off '(' and ')' 441 int state = START; 442 int prevstate = START; 443 parameters = parameters.substring(1, parameters.length() - 1); 444 int cp; 445 for (int index = 0; index < parameters.length(); index += Character.charCount(cp)) { 446 cp = parameters.codePointAt(index); 447 switch (state) { 448 case START: 449 if (Character.isJavaIdentifierStart(cp)) { 450 typeId.append(Character.toChars(cp)); 451 state = TYPE; 452 } 453 prevstate = START; 454 break; 455 case TYPE: 456 if (Character.isJavaIdentifierPart(cp) || cp == '.') { 457 typeId.append(Character.toChars(cp)); 458 } else if (cp == '[') { 459 typeId.append('['); 460 state = ARRAYDECORATION; 461 } else if (Character.isWhitespace(cp)) { 462 state = TNSPACE; 463 } else if (cp == ',') { // no name, just type 464 addTypeToParamList(); 465 state = START; 466 } 467 prevstate = TYPE; 468 break; 469 case TNSPACE: 470 if (Character.isJavaIdentifierStart(cp)) { // name 471 if (prevstate == ARRAYDECORATION) { 472 docenv().warning(holder, 473 "tag.missing_comma_space", 474 name, 475 "(" + parameters + ")"); 476 return (String[])null; 477 } 478 addTypeToParamList(); 479 state = NAME; 480 } else if (cp == '[') { 481 typeId.append('['); 482 state = ARRAYDECORATION; 483 } else if (cp == ',') { // just the type 484 addTypeToParamList(); 485 state = START; 486 } // consume rest all 487 prevstate = TNSPACE; 488 break; 489 case ARRAYDECORATION: 490 if (cp == ']') { 491 typeId.append(']'); 492 state = TNSPACE; 493 } else if (!Character.isWhitespace(cp)) { 494 docenv().warning(holder, 495 "tag.illegal_char_in_arr_dim", 496 name, 497 "(" + parameters + ")"); 498 return (String[])null; 499 } 500 prevstate = ARRAYDECORATION; 501 break; 502 case NAME: 503 if (cp == ',') { // just consume everything till ',' 504 state = START; 505 } 506 prevstate = NAME; 507 break; 508 } 509 } 510 if (state == ARRAYDECORATION || 511 (state == START && prevstate == TNSPACE)) { 512 docenv().warning(holder, 513 "tag.illegal_see_tag", 514 "(" + parameters + ")"); 515 } 516 if (typeId.length() > 0) { 517 paramList.append(typeId.toString()); 518 } 519 return paramList.toArray(new String[paramList.length()]); 520 } 521 addTypeToParamList()522 void addTypeToParamList() { 523 if (typeId.length() > 0) { 524 paramList.append(typeId.toString()); 525 typeId.setLength(0); 526 } 527 } 528 } 529 530 /** 531 * Return the kind of this tag. 532 */ 533 @Override kind()534 public String kind() { 535 return "@see"; 536 } 537 538 /** 539 * Return the label of the see tag. 540 */ label()541 public String label() { 542 return label; 543 } 544 } 545