|
1 /* |
|
2 * Copyright 1997-2005 Sun Microsystems, Inc. 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. Sun designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 * have any questions. |
|
24 */ |
|
25 |
|
26 package com.sun.tools.javadoc; |
|
27 |
|
28 import com.sun.tools.javac.util.*; |
|
29 |
|
30 import com.sun.javadoc.*; |
|
31 |
|
32 /** |
|
33 * Represents a see also documentation tag. |
|
34 * The @see tag can be plain text, or reference a class or member. |
|
35 * |
|
36 * @author Kaiyang Liu (original) |
|
37 * @author Robert Field (rewrite) |
|
38 * @author Atul M Dambalkar |
|
39 * |
|
40 */ |
|
41 class SeeTagImpl extends TagImpl implements SeeTag, LayoutCharacters { |
|
42 |
|
43 //### TODO: Searching for classes, fields, and methods |
|
44 //### should follow the normal rules applied by the compiler. |
|
45 |
|
46 /** |
|
47 * where of where#what - i.e. the class name (may be empty) |
|
48 */ |
|
49 private String where; |
|
50 |
|
51 /** |
|
52 * what of where#what - i.e. the member (may be null) |
|
53 */ |
|
54 private String what; |
|
55 |
|
56 private PackageDoc referencedPackage; |
|
57 private ClassDoc referencedClass; |
|
58 private MemberDoc referencedMember; |
|
59 |
|
60 String label = ""; |
|
61 |
|
62 SeeTagImpl(DocImpl holder, String name, String text) { |
|
63 super(holder, name, text); |
|
64 parseSeeString(); |
|
65 if (where != null) { |
|
66 ClassDocImpl container = null; |
|
67 if (holder instanceof MemberDoc) { |
|
68 container = |
|
69 (ClassDocImpl)((ProgramElementDoc)holder).containingClass(); |
|
70 } else if (holder instanceof ClassDoc) { |
|
71 container = (ClassDocImpl)holder; |
|
72 } |
|
73 findReferenced(container); |
|
74 } |
|
75 } |
|
76 |
|
77 /** |
|
78 * get the class name part of @see, For instance, |
|
79 * if the comment is @see String#startsWith(java.lang.String) . |
|
80 * This function returns String. |
|
81 * Returns null if format was not that of java reference. |
|
82 * Return empty string if class name was not specified.. |
|
83 */ |
|
84 public String referencedClassName() { |
|
85 return where; |
|
86 } |
|
87 |
|
88 /** |
|
89 * get the package referenced by @see. For instance, |
|
90 * if the comment is @see java.lang |
|
91 * This function returns a PackageDocImpl for java.lang |
|
92 * Returns null if no known package found. |
|
93 */ |
|
94 public PackageDoc referencedPackage() { |
|
95 return referencedPackage; |
|
96 } |
|
97 |
|
98 /** |
|
99 * get the class referenced by the class name part of @see, For instance, |
|
100 * if the comment is @see String#startsWith(java.lang.String) . |
|
101 * This function returns a ClassDocImpl for java.lang.String. |
|
102 * Returns null if class is not a class specified on the javadoc command line.. |
|
103 */ |
|
104 public ClassDoc referencedClass() { |
|
105 return referencedClass; |
|
106 } |
|
107 |
|
108 /** |
|
109 * get the name of the member referenced by the prototype part of @see, |
|
110 * For instance, |
|
111 * if the comment is @see String#startsWith(java.lang.String) . |
|
112 * This function returns "startsWith(java.lang.String)" |
|
113 * Returns null if format was not that of java reference. |
|
114 * Return empty string if member name was not specified.. |
|
115 */ |
|
116 public String referencedMemberName() { |
|
117 return what; |
|
118 } |
|
119 |
|
120 /** |
|
121 * get the member referenced by the prototype part of @see, |
|
122 * For instance, |
|
123 * if the comment is @see String#startsWith(java.lang.String) . |
|
124 * This function returns a MethodDocImpl for startsWith. |
|
125 * Returns null if member could not be determined. |
|
126 */ |
|
127 public MemberDoc referencedMember() { |
|
128 return referencedMember; |
|
129 } |
|
130 |
|
131 |
|
132 /** |
|
133 * parse @see part of comment. Determine 'where' and 'what' |
|
134 */ |
|
135 private void parseSeeString() { |
|
136 int len = text.length(); |
|
137 if (len == 0) { |
|
138 return; |
|
139 } |
|
140 switch (text.charAt(0)) { |
|
141 case '<': |
|
142 if (text.charAt(len-1) != '>') { |
|
143 docenv().warning(holder, |
|
144 "tag.see.no_close_bracket_on_url", |
|
145 name, text); |
|
146 } |
|
147 return; |
|
148 case '"': |
|
149 if (len == 1 || text.charAt(len-1) != '"') { |
|
150 docenv().warning(holder, |
|
151 "tag.see.no_close_quote", |
|
152 name, text); |
|
153 } else { |
|
154 // text = text.substring(1,len-1); // strip quotes |
|
155 } |
|
156 return; |
|
157 } |
|
158 |
|
159 // check that the text is one word, with possible parentheses |
|
160 // this part of code doesn't allow |
|
161 // @see <a href=.....>asfd</a> |
|
162 // comment it. |
|
163 |
|
164 // the code assumes that there is no initial white space. |
|
165 int parens = 0; |
|
166 int commentstart = 0; |
|
167 int start = 0; |
|
168 int cp; |
|
169 for (int i = start; i < len ; i += Character.charCount(cp)) { |
|
170 cp = text.codePointAt(i); |
|
171 switch (cp) { |
|
172 case '(': parens++; break; |
|
173 case ')': parens--; break; |
|
174 case '[': case ']': case '.': case '#': break; |
|
175 case ',': |
|
176 if (parens <= 0) { |
|
177 docenv().warning(holder, |
|
178 "tag.see.malformed_see_tag", |
|
179 name, text); |
|
180 return; |
|
181 } |
|
182 break; |
|
183 case ' ': case '\t': case '\n': case CR: |
|
184 if (parens == 0) { //here onwards the comment starts. |
|
185 commentstart = i; |
|
186 i = len; |
|
187 } |
|
188 break; |
|
189 default: |
|
190 if (!Character.isJavaIdentifierPart(cp)) { |
|
191 docenv().warning(holder, |
|
192 "tag.see.illegal_character", |
|
193 name, ""+cp, text); |
|
194 } |
|
195 break; |
|
196 } |
|
197 } |
|
198 if (parens != 0) { |
|
199 docenv().warning(holder, |
|
200 "tag.see.malformed_see_tag", |
|
201 name, text); |
|
202 return; |
|
203 } |
|
204 |
|
205 String seetext = ""; |
|
206 String labeltext = ""; |
|
207 |
|
208 if (commentstart > 0) { |
|
209 seetext = text.substring(start, commentstart); |
|
210 labeltext = text.substring(commentstart + 1); |
|
211 // strip off the white space which can be between seetext and the |
|
212 // actual label. |
|
213 for (int i = 0; i < labeltext.length(); i++) { |
|
214 char ch2 = labeltext.charAt(i); |
|
215 if (!(ch2 == ' ' || ch2 == '\t' || ch2 == '\n')) { |
|
216 label = labeltext.substring(i); |
|
217 break; |
|
218 } |
|
219 } |
|
220 } else { |
|
221 seetext = text; |
|
222 label = ""; |
|
223 } |
|
224 |
|
225 int sharp = seetext.indexOf('#'); |
|
226 if (sharp >= 0) { |
|
227 // class#member |
|
228 where = seetext.substring(0, sharp); |
|
229 what = seetext.substring(sharp + 1); |
|
230 } else { |
|
231 if (seetext.indexOf('(') >= 0) { |
|
232 docenv().warning(holder, |
|
233 "tag.see.missing_sharp", |
|
234 name, text); |
|
235 where = ""; |
|
236 what = seetext; |
|
237 } |
|
238 else { |
|
239 // no member specified, text names class |
|
240 where = seetext; |
|
241 what = null; |
|
242 } |
|
243 } |
|
244 } |
|
245 |
|
246 /** |
|
247 * Find what is referenced by the see also. If possible, sets |
|
248 * referencedClass and referencedMember. |
|
249 * |
|
250 * @param containingClass the class containing the comment containing |
|
251 * the tag. May be null, if, for example, it is a package comment. |
|
252 */ |
|
253 private void findReferenced(ClassDocImpl containingClass) { |
|
254 if (where.length() > 0) { |
|
255 if (containingClass != null) { |
|
256 referencedClass = containingClass.findClass(where); |
|
257 } else { |
|
258 referencedClass = docenv().lookupClass(where); |
|
259 } |
|
260 if (referencedClass == null && holder() instanceof ProgramElementDoc) { |
|
261 referencedClass = docenv().lookupClass( |
|
262 ((ProgramElementDoc) holder()).containingPackage().name() + "." + where); |
|
263 } |
|
264 |
|
265 if (referencedClass == null) { /* may just not be in this run */ |
|
266 // docenv().warning(holder, "tag.see.class_not_found", |
|
267 // where, text); |
|
268 // check if it's a package name |
|
269 referencedPackage = docenv().lookupPackage(where); |
|
270 return; |
|
271 } |
|
272 } else { |
|
273 if (containingClass == null) { |
|
274 docenv().warning(holder, |
|
275 "tag.see.class_not_specified", |
|
276 name, text); |
|
277 return; |
|
278 } else { |
|
279 referencedClass = containingClass; |
|
280 } |
|
281 } |
|
282 where = referencedClass.qualifiedName(); |
|
283 |
|
284 if (what == null) { |
|
285 return; |
|
286 } else { |
|
287 int paren = what.indexOf('('); |
|
288 String memName = (paren >= 0 ? what.substring(0, paren) : what); |
|
289 String[] paramarr; |
|
290 if (paren > 0) { |
|
291 // has parameter list -- should be method or constructor |
|
292 paramarr = new ParameterParseMachine(what. |
|
293 substring(paren, what.length())).parseParameters(); |
|
294 if (paramarr != null) { |
|
295 referencedMember = findExecutableMember(memName, paramarr, |
|
296 referencedClass); |
|
297 } else { |
|
298 referencedMember = null; |
|
299 } |
|
300 } else { |
|
301 // no parameter list -- should be field |
|
302 referencedMember = findExecutableMember(memName, null, |
|
303 referencedClass); |
|
304 FieldDoc fd = ((ClassDocImpl)referencedClass). |
|
305 findField(memName); |
|
306 // when no args given, prefer fields over methods |
|
307 if (referencedMember == null || |
|
308 (fd != null && |
|
309 fd.containingClass() |
|
310 .subclassOf(referencedMember.containingClass()))) { |
|
311 referencedMember = fd; |
|
312 } |
|
313 } |
|
314 if (referencedMember == null) { |
|
315 docenv().warning(holder, |
|
316 "tag.see.can_not_find_member", |
|
317 name, what, where); |
|
318 } |
|
319 } |
|
320 } |
|
321 |
|
322 private MemberDoc findReferencedMethod(String memName, String[] paramarr, |
|
323 ClassDoc referencedClass) { |
|
324 MemberDoc meth = findExecutableMember(memName, paramarr, referencedClass); |
|
325 ClassDoc[] nestedclasses = referencedClass.innerClasses(); |
|
326 if (meth == null) { |
|
327 for (int i = 0; i < nestedclasses.length; i++) { |
|
328 meth = findReferencedMethod(memName, paramarr, nestedclasses[i]); |
|
329 if (meth != null) { |
|
330 return meth; |
|
331 } |
|
332 } |
|
333 } |
|
334 return null; |
|
335 } |
|
336 |
|
337 private MemberDoc findExecutableMember(String memName, String[] paramarr, |
|
338 ClassDoc referencedClass) { |
|
339 if (memName.equals(referencedClass.name())) { |
|
340 return ((ClassDocImpl)referencedClass).findConstructor(memName, |
|
341 paramarr); |
|
342 } else { // it's a method. |
|
343 return ((ClassDocImpl)referencedClass).findMethod(memName, |
|
344 paramarr); |
|
345 } |
|
346 } |
|
347 |
|
348 // separate "int, String" from "(int, String)" |
|
349 // (int i, String s) ==> [0] = "int", [1] = String |
|
350 // (int[][], String[]) ==> [0] = "int[][]" // [1] = "String[]" |
|
351 class ParameterParseMachine { |
|
352 final int START = 0; |
|
353 final int TYPE = 1; |
|
354 final int NAME = 2; |
|
355 final int TNSPACE = 3; // space between type and name |
|
356 final int ARRAYDECORATION = 4; |
|
357 final int ARRAYSPACE = 5; |
|
358 |
|
359 String parameters; |
|
360 |
|
361 StringBuffer typeId; |
|
362 |
|
363 ListBuffer<String> paramList; |
|
364 |
|
365 ParameterParseMachine(String parameters) { |
|
366 this.parameters = parameters; |
|
367 this.paramList = new ListBuffer<String>(); |
|
368 typeId = new StringBuffer(); |
|
369 } |
|
370 |
|
371 public String[] parseParameters() { |
|
372 if (parameters.equals("()")) { |
|
373 return new String[0]; |
|
374 } // now strip off '(' and ')' |
|
375 int state = START; |
|
376 int prevstate = START; |
|
377 parameters = parameters.substring(1, parameters.length() - 1); |
|
378 int cp; |
|
379 for (int index = 0; index < parameters.length(); index += Character.charCount(cp)) { |
|
380 cp = parameters.codePointAt(index); |
|
381 switch (state) { |
|
382 case START: |
|
383 if (Character.isJavaIdentifierStart(cp)) { |
|
384 typeId.append(Character.toChars(cp)); |
|
385 state = TYPE; |
|
386 } |
|
387 prevstate = START; |
|
388 break; |
|
389 case TYPE: |
|
390 if (Character.isJavaIdentifierPart(cp) || cp == '.') { |
|
391 typeId.append(Character.toChars(cp)); |
|
392 } else if (cp == '[') { |
|
393 typeId.append('['); |
|
394 state = ARRAYDECORATION; |
|
395 } else if (Character.isWhitespace(cp)) { |
|
396 state = TNSPACE; |
|
397 } else if (cp == ',') { // no name, just type |
|
398 addTypeToParamList(); |
|
399 state = START; |
|
400 } |
|
401 prevstate = TYPE; |
|
402 break; |
|
403 case TNSPACE: |
|
404 if (Character.isJavaIdentifierStart(cp)) { // name |
|
405 if (prevstate == ARRAYDECORATION) { |
|
406 docenv().warning(holder, |
|
407 "tag.missing_comma_space", |
|
408 name, |
|
409 "(" + parameters + ")"); |
|
410 return (String[])null; |
|
411 } |
|
412 addTypeToParamList(); |
|
413 state = NAME; |
|
414 } else if (cp == '[') { |
|
415 typeId.append('['); |
|
416 state = ARRAYDECORATION; |
|
417 } else if (cp == ',') { // just the type |
|
418 addTypeToParamList(); |
|
419 state = START; |
|
420 } // consume rest all |
|
421 prevstate = TNSPACE; |
|
422 break; |
|
423 case ARRAYDECORATION: |
|
424 if (cp == ']') { |
|
425 typeId.append(']'); |
|
426 state = TNSPACE; |
|
427 } else if (!Character.isWhitespace(cp)) { |
|
428 docenv().warning(holder, |
|
429 "tag.illegal_char_in_arr_dim", |
|
430 name, |
|
431 "(" + parameters + ")"); |
|
432 return (String[])null; |
|
433 } |
|
434 prevstate = ARRAYDECORATION; |
|
435 break; |
|
436 case NAME: |
|
437 if (cp == ',') { // just consume everything till ',' |
|
438 state = START; |
|
439 } |
|
440 prevstate = NAME; |
|
441 break; |
|
442 } |
|
443 } |
|
444 if (state == ARRAYDECORATION || |
|
445 (state == START && prevstate == TNSPACE)) { |
|
446 docenv().warning(holder, |
|
447 "tag.illegal_see_tag", |
|
448 "(" + parameters + ")"); |
|
449 } |
|
450 if (typeId.length() > 0) { |
|
451 paramList.append(typeId.toString()); |
|
452 } |
|
453 return (String[])paramList.toArray(new String[paramList.length()]); |
|
454 } |
|
455 |
|
456 void addTypeToParamList() { |
|
457 if (typeId.length() > 0) { |
|
458 paramList.append(typeId.toString()); |
|
459 typeId.setLength(0); |
|
460 } |
|
461 } |
|
462 } |
|
463 |
|
464 /** |
|
465 * Return the kind of this tag. |
|
466 */ |
|
467 public String kind() { |
|
468 return "@see"; |
|
469 } |
|
470 |
|
471 /** |
|
472 * Return the label of the see tag. |
|
473 */ |
|
474 public String label() { |
|
475 return label; |
|
476 } |
|
477 } |