Mon, 15 Oct 2012 17:07:55 -0700
8000666: javadoc should write directly to Writer instead of composing strings
Reviewed-by: bpatel
1 /*
2 * Copyright (c) 1999, 2012, 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 */
26 package com.sun.tools.doclets.internal.toolkit.util;
28 import java.io.*;
29 import java.util.*;
31 import com.sun.javadoc.*;
32 import com.sun.tools.doclets.internal.toolkit.*;
34 /**
35 * Utilities Class for Doclets.
36 *
37 * <p><b>This is NOT part of any supported API.
38 * If you write code that depends on this, you do so at your own risk.
39 * This code and its internal interfaces are subject to change or
40 * deletion without notice.</b>
41 *
42 * @author Atul M Dambalkar
43 * @author Jamie Ho
44 */
45 public class Util {
47 /**
48 * A mapping between characters and their
49 * corresponding HTML escape character.
50 */
51 public static final String[][] HTML_ESCAPE_CHARS =
52 {{"&", "&"}, {"<", "<"}, {">", ">"}};
54 /**
55 * Name of the resource directory.
56 */
57 public static final String RESOURCESDIR = "resources";
59 /**
60 * Return array of class members whose documentation is to be generated.
61 * If the member is deprecated do not include such a member in the
62 * returned array.
63 *
64 * @param members Array of members to choose from.
65 * @return ProgramElementDoc[] Array of eligible members for whom
66 * documentation is getting generated.
67 */
68 public static ProgramElementDoc[] excludeDeprecatedMembers(
69 ProgramElementDoc[] members) {
70 return
71 toProgramElementDocArray(excludeDeprecatedMembersAsList(members));
72 }
74 /**
75 * Return array of class members whose documentation is to be generated.
76 * If the member is deprecated do not include such a member in the
77 * returned array.
78 *
79 * @param members Array of members to choose from.
80 * @return List List of eligible members for whom
81 * documentation is getting generated.
82 */
83 public static List<ProgramElementDoc> excludeDeprecatedMembersAsList(
84 ProgramElementDoc[] members) {
85 List<ProgramElementDoc> list = new ArrayList<ProgramElementDoc>();
86 for (int i = 0; i < members.length; i++) {
87 if (members[i].tags("deprecated").length == 0) {
88 list.add(members[i]);
89 }
90 }
91 Collections.sort(list);
92 return list;
93 }
95 /**
96 * Return the list of ProgramElementDoc objects as Array.
97 */
98 public static ProgramElementDoc[] toProgramElementDocArray(List<ProgramElementDoc> list) {
99 ProgramElementDoc[] pgmarr = new ProgramElementDoc[list.size()];
100 for (int i = 0; i < list.size(); i++) {
101 pgmarr[i] = list.get(i);
102 }
103 return pgmarr;
104 }
106 /**
107 * Return true if a non-public member found in the given array.
108 *
109 * @param members Array of members to look into.
110 * @return boolean True if non-public member found, false otherwise.
111 */
112 public static boolean nonPublicMemberFound(ProgramElementDoc[] members) {
113 for (int i = 0; i < members.length; i++) {
114 if (!members[i].isPublic()) {
115 return true;
116 }
117 }
118 return false;
119 }
121 /**
122 * Search for the given method in the given class.
123 *
124 * @param cd Class to search into.
125 * @param method Method to be searched.
126 * @return MethodDoc Method found, null otherwise.
127 */
128 public static MethodDoc findMethod(ClassDoc cd, MethodDoc method) {
129 MethodDoc[] methods = cd.methods();
130 for (int i = 0; i < methods.length; i++) {
131 if (executableMembersEqual(method, methods[i])) {
132 return methods[i];
134 }
135 }
136 return null;
137 }
139 /**
140 * @param member1 the first method to compare.
141 * @param member2 the second method to compare.
142 * @return true if member1 overrides/hides or is overriden/hidden by member2.
143 */
144 public static boolean executableMembersEqual(ExecutableMemberDoc member1,
145 ExecutableMemberDoc member2) {
146 if (! (member1 instanceof MethodDoc && member2 instanceof MethodDoc))
147 return false;
149 MethodDoc method1 = (MethodDoc) member1;
150 MethodDoc method2 = (MethodDoc) member2;
151 if (method1.isStatic() && method2.isStatic()) {
152 Parameter[] targetParams = method1.parameters();
153 Parameter[] currentParams;
154 if (method1.name().equals(method2.name()) &&
155 (currentParams = method2.parameters()).length ==
156 targetParams.length) {
157 int j;
158 for (j = 0; j < targetParams.length; j++) {
159 if (! (targetParams[j].typeName().equals(
160 currentParams[j].typeName()) ||
161 currentParams[j].type() instanceof TypeVariable ||
162 targetParams[j].type() instanceof TypeVariable)) {
163 break;
164 }
165 }
166 if (j == targetParams.length) {
167 return true;
168 }
169 }
170 return false;
171 } else {
172 return method1.overrides(method2) ||
173 method2.overrides(method1) ||
174 member1 == member2;
175 }
176 }
178 /**
179 * According to
180 * <cite>The Java™ Language Specification</cite>,
181 * all the outer classes and static inner classes are core classes.
182 */
183 public static boolean isCoreClass(ClassDoc cd) {
184 return cd.containingClass() == null || cd.isStatic();
185 }
187 public static boolean matches(ProgramElementDoc doc1,
188 ProgramElementDoc doc2) {
189 if (doc1 instanceof ExecutableMemberDoc &&
190 doc2 instanceof ExecutableMemberDoc) {
191 ExecutableMemberDoc ed1 = (ExecutableMemberDoc)doc1;
192 ExecutableMemberDoc ed2 = (ExecutableMemberDoc)doc2;
193 return executableMembersEqual(ed1, ed2);
194 } else {
195 return doc1.name().equals(doc2.name());
196 }
197 }
199 /**
200 * Copy source file to destination file.
201 *
202 * @throws SecurityException
203 * @throws IOException
204 */
205 public static void copyFile(File destfile, File srcfile)
206 throws IOException {
207 byte[] bytearr = new byte[512];
208 int len = 0;
209 FileInputStream input = new FileInputStream(srcfile);
210 File destDir = destfile.getParentFile();
211 destDir.mkdirs();
212 FileOutputStream output = new FileOutputStream(destfile);
213 try {
214 while ((len = input.read(bytearr)) != -1) {
215 output.write(bytearr, 0, len);
216 }
217 } catch (FileNotFoundException exc) {
218 } catch (SecurityException exc) {
219 } finally {
220 input.close();
221 output.close();
222 }
223 }
225 /**
226 * Copy the given directory contents from the source package directory
227 * to the generated documentation directory. For example for a package
228 * java.lang this method find out the source location of the package using
229 * {@link SourcePath} and if given directory is found in the source
230 * directory structure, copy the entire directory, to the generated
231 * documentation hierarchy.
232 *
233 * @param configuration The configuration of the current doclet.
234 * @param path The relative path to the directory to be copied.
235 * @param dir The original directory name to copy from.
236 * @param overwrite Overwrite files if true.
237 */
238 public static void copyDocFiles(Configuration configuration,
239 String path, String dir, boolean overwrite) {
240 if (checkCopyDocFilesErrors(configuration, path, dir)) {
241 return;
242 }
243 String destname = configuration.docFileDestDirName;
244 File srcdir = new File(path + dir);
245 if (destname.length() > 0 && !destname.endsWith(
246 DirectoryManager.URL_FILE_SEPARATOR)) {
247 destname += DirectoryManager.URL_FILE_SEPARATOR;
248 }
249 String dest = destname + dir;
250 try {
251 File destdir = new File(dest);
252 DirectoryManager.createDirectory(configuration, dest);
253 String[] files = srcdir.list();
254 for (int i = 0; i < files.length; i++) {
255 File srcfile = new File(srcdir, files[i]);
256 File destfile = new File(destdir, files[i]);
257 if (srcfile.isFile()) {
258 if(destfile.exists() && ! overwrite) {
259 configuration.message.warning((SourcePosition) null,
260 "doclet.Copy_Overwrite_warning",
261 srcfile.toString(), destdir.toString());
262 } else {
263 configuration.message.notice(
264 "doclet.Copying_File_0_To_Dir_1",
265 srcfile.toString(), destdir.toString());
266 Util.copyFile(destfile, srcfile);
267 }
268 } else if(srcfile.isDirectory()) {
269 if(configuration.copydocfilesubdirs
270 && ! configuration.shouldExcludeDocFileDir(
271 srcfile.getName())){
272 copyDocFiles(configuration, path, dir +
273 DirectoryManager.URL_FILE_SEPARATOR + srcfile.getName(),
274 overwrite);
275 }
276 }
277 }
278 } catch (SecurityException exc) {
279 throw new DocletAbortException();
280 } catch (IOException exc) {
281 throw new DocletAbortException();
282 }
283 }
285 /**
286 * Given the parameters for copying doc-files, check for errors.
287 *
288 * @param configuration The configuration of the current doclet.
289 * @param path The relative path to the directory to be copied.
290 * @param dirName The original directory name to copy from.
291 */
292 private static boolean checkCopyDocFilesErrors (Configuration configuration,
293 String path, String dirName) {
294 if ((configuration.sourcepath == null || configuration.sourcepath.length() == 0) &&
295 (configuration.destDirName == null || configuration.destDirName.length() == 0)) {
296 //The destination path and source path are definitely equal.
297 return true;
298 }
299 File sourcePath, destPath = new File(configuration.destDirName);
300 StringTokenizer pathTokens = new StringTokenizer(
301 configuration.sourcepath == null ? "" : configuration.sourcepath,
302 File.pathSeparator);
303 //Check if the destination path is equal to the source path. If yes,
304 //do not copy doc-file directories.
305 while(pathTokens.hasMoreTokens()){
306 sourcePath = new File(pathTokens.nextToken());
307 if(destPath.equals(sourcePath)){
308 return true;
309 }
310 }
311 //Make sure the doc-file being copied exists.
312 File srcdir = new File(path + dirName);
313 if (! srcdir.exists()) {
314 return true;
315 }
316 return false;
317 }
319 /**
320 * Copy a file in the resources directory to the destination
321 * directory (if it is not there already). If
322 * <code>overwrite</code> is true and the destination file
323 * already exists, overwrite it.
324 *
325 * @param configuration Holds the destination directory and error message
326 * @param resourcefile The name of the resource file to copy
327 * @param overwrite A flag to indicate whether the file in the
328 * destination directory will be overwritten if
329 * it already exists.
330 */
331 public static void copyResourceFile(Configuration configuration,
332 String resourcefile, boolean overwrite) {
333 String destresourcesdir = configuration.destDirName + RESOURCESDIR;
334 copyFile(configuration, resourcefile, RESOURCESDIR, destresourcesdir,
335 overwrite, false);
336 }
338 /**
339 * Copy a file from a source directory to a destination directory
340 * (if it is not there already). If <code>overwrite</code> is true and
341 * the destination file already exists, overwrite it.
342 *
343 * @param configuration Holds the error message
344 * @param file The name of the file to copy
345 * @param source The source directory
346 * @param destination The destination directory where the file needs to be copied
347 * @param overwrite A flag to indicate whether the file in the
348 * destination directory will be overwritten if
349 * it already exists.
350 * @param replaceNewLine true if the newline needs to be replaced with platform-
351 * specific newline.
352 */
353 public static void copyFile(Configuration configuration, String file, String source,
354 String destination, boolean overwrite, boolean replaceNewLine) {
355 DirectoryManager.createDirectory(configuration, destination);
356 File destfile = new File(destination, file);
357 if(destfile.exists() && (! overwrite)) return;
358 try {
359 InputStream in = Configuration.class.getResourceAsStream(
360 source + DirectoryManager.URL_FILE_SEPARATOR + file);
361 if(in==null) return;
362 OutputStream out = new FileOutputStream(destfile);
363 try {
364 if (!replaceNewLine) {
365 byte[] buf = new byte[2048];
366 int n;
367 while((n = in.read(buf))>0) out.write(buf,0,n);
368 } else {
369 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
370 BufferedWriter writer;
371 if (configuration.docencoding == null) {
372 writer = new BufferedWriter(new OutputStreamWriter(out));
373 } else {
374 writer = new BufferedWriter(new OutputStreamWriter(out,
375 configuration.docencoding));
376 }
377 try {
378 String line;
379 while ((line = reader.readLine()) != null) {
380 writer.write(line);
381 writer.write(DocletConstants.NL);
382 }
383 } finally {
384 reader.close();
385 writer.close();
386 }
387 }
388 } finally {
389 in.close();
390 out.close();
391 }
392 } catch (IOException ie) {
393 ie.printStackTrace(System.err);
394 throw new DocletAbortException();
395 }
396 }
398 /**
399 * Given a PackageDoc, return the source path for that package.
400 * @param configuration The Configuration for the current Doclet.
401 * @param pkgDoc The package to seach the path for.
402 * @return A string representing the path to the given package.
403 */
404 public static String getPackageSourcePath(Configuration configuration,
405 PackageDoc pkgDoc){
406 try{
407 String pkgPath = DirectoryManager.getDirectoryPath(pkgDoc);
408 String completePath = new SourcePath(configuration.sourcepath).
409 getDirectory(pkgPath) + DirectoryManager.URL_FILE_SEPARATOR;
410 //Make sure that both paths are using the same separators.
411 completePath = Util.replaceText(completePath, File.separator,
412 DirectoryManager.URL_FILE_SEPARATOR);
413 pkgPath = Util.replaceText(pkgPath, File.separator,
414 DirectoryManager.URL_FILE_SEPARATOR);
415 return completePath.substring(0, completePath.lastIndexOf(pkgPath));
416 } catch (Exception e){
417 return "";
418 }
419 }
421 /**
422 * We want the list of types in alphabetical order. However, types are not
423 * comparable. We need a comparator for now.
424 */
425 private static class TypeComparator implements Comparator<Type> {
426 public int compare(Type type1, Type type2) {
427 return type1.qualifiedTypeName().toLowerCase().compareTo(
428 type2.qualifiedTypeName().toLowerCase());
429 }
430 }
432 /**
433 * For the class return all implemented interfaces including the
434 * superinterfaces of the implementing interfaces, also iterate over for
435 * all the superclasses. For interface return all the extended interfaces
436 * as well as superinterfaces for those extended interfaces.
437 *
438 * @param type type whose implemented or
439 * super interfaces are sought.
440 * @param configuration the current configuration of the doclet.
441 * @param sort if true, return list of interfaces sorted alphabetically.
442 * @return List of all the required interfaces.
443 */
444 public static List<Type> getAllInterfaces(Type type,
445 Configuration configuration, boolean sort) {
446 Map<ClassDoc,Type> results = sort ? new TreeMap<ClassDoc,Type>() : new LinkedHashMap<ClassDoc,Type>();
447 Type[] interfaceTypes = null;
448 Type superType = null;
449 if (type instanceof ParameterizedType) {
450 interfaceTypes = ((ParameterizedType) type).interfaceTypes();
451 superType = ((ParameterizedType) type).superclassType();
452 } else if (type instanceof ClassDoc) {
453 interfaceTypes = ((ClassDoc) type).interfaceTypes();
454 superType = ((ClassDoc) type).superclassType();
455 } else {
456 interfaceTypes = type.asClassDoc().interfaceTypes();
457 superType = type.asClassDoc().superclassType();
458 }
460 for (int i = 0; i < interfaceTypes.length; i++) {
461 Type interfaceType = interfaceTypes[i];
462 ClassDoc interfaceClassDoc = interfaceType.asClassDoc();
463 if (! (interfaceClassDoc.isPublic() ||
464 (configuration == null ||
465 isLinkable(interfaceClassDoc, configuration)))) {
466 continue;
467 }
468 results.put(interfaceClassDoc, interfaceType);
469 List<Type> superInterfaces = getAllInterfaces(interfaceType, configuration, sort);
470 for (Iterator<Type> iter = superInterfaces.iterator(); iter.hasNext(); ) {
471 Type t = iter.next();
472 results.put(t.asClassDoc(), t);
473 }
474 }
475 if (superType == null)
476 return new ArrayList<Type>(results.values());
477 //Try walking the tree.
478 addAllInterfaceTypes(results,
479 superType,
480 superType instanceof ClassDoc ?
481 ((ClassDoc) superType).interfaceTypes() :
482 ((ParameterizedType) superType).interfaceTypes(),
483 false, configuration);
484 List<Type> resultsList = new ArrayList<Type>(results.values());
485 if (sort) {
486 Collections.sort(resultsList, new TypeComparator());
487 }
488 return resultsList;
489 }
491 public static List<Type> getAllInterfaces(Type type, Configuration configuration) {
492 return getAllInterfaces(type, configuration, true);
493 }
495 private static void findAllInterfaceTypes(Map<ClassDoc,Type> results, ClassDoc c, boolean raw,
496 Configuration configuration) {
497 Type superType = c.superclassType();
498 if (superType == null)
499 return;
500 addAllInterfaceTypes(results, superType,
501 superType instanceof ClassDoc ?
502 ((ClassDoc) superType).interfaceTypes() :
503 ((ParameterizedType) superType).interfaceTypes(),
504 raw, configuration);
505 }
507 private static void findAllInterfaceTypes(Map<ClassDoc,Type> results, ParameterizedType p,
508 Configuration configuration) {
509 Type superType = p.superclassType();
510 if (superType == null)
511 return;
512 addAllInterfaceTypes(results, superType,
513 superType instanceof ClassDoc ?
514 ((ClassDoc) superType).interfaceTypes() :
515 ((ParameterizedType) superType).interfaceTypes(),
516 false, configuration);
517 }
519 private static void addAllInterfaceTypes(Map<ClassDoc,Type> results, Type type,
520 Type[] interfaceTypes, boolean raw,
521 Configuration configuration) {
522 for (int i = 0; i < interfaceTypes.length; i++) {
523 Type interfaceType = interfaceTypes[i];
524 ClassDoc interfaceClassDoc = interfaceType.asClassDoc();
525 if (! (interfaceClassDoc.isPublic() ||
526 (configuration != null &&
527 isLinkable(interfaceClassDoc, configuration)))) {
528 continue;
529 }
530 if (raw)
531 interfaceType = interfaceType.asClassDoc();
532 results.put(interfaceClassDoc, interfaceType);
533 List<Type> superInterfaces = getAllInterfaces(interfaceType, configuration);
534 for (Iterator<Type> iter = superInterfaces.iterator(); iter.hasNext(); ) {
535 Type superInterface = iter.next();
536 results.put(superInterface.asClassDoc(), superInterface);
537 }
538 }
539 if (type instanceof ParameterizedType)
540 findAllInterfaceTypes(results, (ParameterizedType) type, configuration);
541 else if (((ClassDoc) type).typeParameters().length == 0)
542 findAllInterfaceTypes(results, (ClassDoc) type, raw, configuration);
543 else
544 findAllInterfaceTypes(results, (ClassDoc) type, true, configuration);
545 }
547 /**
548 * Enclose in quotes, used for paths and filenames that contains spaces
549 */
550 public static String quote(String filepath) {
551 return ("\"" + filepath + "\"");
552 }
554 /**
555 * Given a package, return it's name.
556 * @param packageDoc the package to check.
557 * @return the name of the given package.
558 */
559 public static String getPackageName(PackageDoc packageDoc) {
560 return packageDoc == null || packageDoc.name().length() == 0 ?
561 DocletConstants.DEFAULT_PACKAGE_NAME : packageDoc.name();
562 }
564 /**
565 * Given a package, return it's file name without the extension.
566 * @param packageDoc the package to check.
567 * @return the file name of the given package.
568 */
569 public static String getPackageFileHeadName(PackageDoc packageDoc) {
570 return packageDoc == null || packageDoc.name().length() == 0 ?
571 DocletConstants.DEFAULT_PACKAGE_FILE_NAME : packageDoc.name();
572 }
574 /**
575 * Given a string, replace all occurraces of 'newStr' with 'oldStr'.
576 * @param originalStr the string to modify.
577 * @param oldStr the string to replace.
578 * @param newStr the string to insert in place of the old string.
579 */
580 public static String replaceText(String originalStr, String oldStr,
581 String newStr) {
582 if (oldStr == null || newStr == null || oldStr.equals(newStr)) {
583 return originalStr;
584 }
585 return originalStr.replace(oldStr, newStr);
586 }
588 /**
589 * Given a string, escape all special html characters and
590 * return the result.
591 *
592 * @param s The string to check.
593 * @return the original string with all of the HTML characters
594 * escaped.
595 *
596 * @see #HTML_ESCAPE_CHARS
597 */
598 public static String escapeHtmlChars(String s) {
599 String result = s;
600 for (int i = 0; i < HTML_ESCAPE_CHARS.length; i++) {
601 result = Util.replaceText(result,
602 HTML_ESCAPE_CHARS[i][0], HTML_ESCAPE_CHARS[i][1]);
603 }
604 return result;
605 }
607 /**
608 * Given a string, strips all html characters and
609 * return the result.
610 *
611 * @param rawString The string to check.
612 * @return the original string with all of the HTML characters
613 * stripped.
614 *
615 */
616 public static String stripHtml(String rawString) {
617 // remove HTML tags
618 rawString = rawString.replaceAll("\\<.*?>", " ");
619 // consolidate multiple spaces between a word to a single space
620 rawString = rawString.replaceAll("\\b\\s{2,}\\b", " ");
621 // remove extra whitespaces
622 return rawString.trim();
623 }
625 /**
626 * Create the directory path for the file to be generated, construct
627 * FileOutputStream and OutputStreamWriter depending upon docencoding.
628 *
629 * @param path The directory path to be created for this file.
630 * @param filename File Name to which the PrintWriter will do the Output.
631 * @param docencoding Encoding to be used for this file.
632 * @exception IOException Exception raised by the FileWriter is passed on
633 * to next level.
634 * @exception UnsupportedEncodingException Exception raised by the
635 * OutputStreamWriter is passed on to next level.
636 * @return Writer Writer for the file getting generated.
637 * @see java.io.FileOutputStream
638 * @see java.io.OutputStreamWriter
639 */
640 public static Writer genWriter(Configuration configuration,
641 String path, String filename,
642 String docencoding)
643 throws IOException, UnsupportedEncodingException {
644 FileOutputStream fos;
645 if (path != null) {
646 DirectoryManager.createDirectory(configuration, path);
647 fos = new FileOutputStream(((path.length() > 0)?
648 path + File.separator: "") + filename);
649 } else {
650 fos = new FileOutputStream(filename);
651 }
652 if (docencoding == null) {
653 return new BufferedWriter(new OutputStreamWriter(fos));
654 } else {
655 return new BufferedWriter(new OutputStreamWriter(fos, docencoding));
656 }
657 }
659 /**
660 * Given an annotation, return true if it should be documented and false
661 * otherwise.
662 *
663 * @param annotationDoc the annotation to check.
664 *
665 * @return true return true if it should be documented and false otherwise.
666 */
667 public static boolean isDocumentedAnnotation(AnnotationTypeDoc annotationDoc) {
668 AnnotationDesc[] annotationDescList = annotationDoc.annotations();
669 for (int i = 0; i < annotationDescList.length; i++) {
670 if (annotationDescList[i].annotationType().qualifiedName().equals(
671 java.lang.annotation.Documented.class.getName())){
672 return true;
673 }
674 }
675 return false;
676 }
678 /**
679 * Given a string, return an array of tokens. The separator can be escaped
680 * with the '\' character. The '\' character may also be escaped by the
681 * '\' character.
682 *
683 * @param s the string to tokenize.
684 * @param separator the separator char.
685 * @param maxTokens the maxmimum number of tokens returned. If the
686 * max is reached, the remaining part of s is appended
687 * to the end of the last token.
688 *
689 * @return an array of tokens.
690 */
691 public static String[] tokenize(String s, char separator, int maxTokens) {
692 List<String> tokens = new ArrayList<String>();
693 StringBuilder token = new StringBuilder ();
694 boolean prevIsEscapeChar = false;
695 for (int i = 0; i < s.length(); i += Character.charCount(i)) {
696 int currentChar = s.codePointAt(i);
697 if (prevIsEscapeChar) {
698 // Case 1: escaped character
699 token.appendCodePoint(currentChar);
700 prevIsEscapeChar = false;
701 } else if (currentChar == separator && tokens.size() < maxTokens-1) {
702 // Case 2: separator
703 tokens.add(token.toString());
704 token = new StringBuilder();
705 } else if (currentChar == '\\') {
706 // Case 3: escape character
707 prevIsEscapeChar = true;
708 } else {
709 // Case 4: regular character
710 token.appendCodePoint(currentChar);
711 }
712 }
713 if (token.length() > 0) {
714 tokens.add(token.toString());
715 }
716 return tokens.toArray(new String[] {});
717 }
719 /**
720 * Return true if this class is linkable and false if we can't link to the
721 * desired class.
722 * <br>
723 * <b>NOTE:</b> You can only link to external classes if they are public or
724 * protected.
725 *
726 * @param classDoc the class to check.
727 * @param configuration the current configuration of the doclet.
728 *
729 * @return true if this class is linkable and false if we can't link to the
730 * desired class.
731 */
732 public static boolean isLinkable(ClassDoc classDoc,
733 Configuration configuration) {
734 return
735 ((classDoc.isIncluded() && configuration.isGeneratedDoc(classDoc))) ||
736 (configuration.extern.isExternal(classDoc) &&
737 (classDoc.isPublic() || classDoc.isProtected()));
738 }
740 /**
741 * Given a class, return the closest visible super class.
742 *
743 * @param classDoc the class we are searching the parent for.
744 * @param configuration the current configuration of the doclet.
745 * @return the closest visible super class. Return null if it cannot
746 * be found (i.e. classDoc is java.lang.Object).
747 */
748 public static Type getFirstVisibleSuperClass(ClassDoc classDoc,
749 Configuration configuration) {
750 if (classDoc == null) {
751 return null;
752 }
753 Type sup = classDoc.superclassType();
754 ClassDoc supClassDoc = classDoc.superclass();
755 while (sup != null &&
756 (! (supClassDoc.isPublic() ||
757 isLinkable(supClassDoc, configuration))) ) {
758 if (supClassDoc.superclass().qualifiedName().equals(supClassDoc.qualifiedName()))
759 break;
760 sup = supClassDoc.superclassType();
761 supClassDoc = supClassDoc.superclass();
762 }
763 if (classDoc.equals(supClassDoc)) {
764 return null;
765 }
766 return sup;
767 }
769 /**
770 * Given a class, return the closest visible super class.
771 *
772 * @param classDoc the class we are searching the parent for.
773 * @param configuration the current configuration of the doclet.
774 * @return the closest visible super class. Return null if it cannot
775 * be found (i.e. classDoc is java.lang.Object).
776 */
777 public static ClassDoc getFirstVisibleSuperClassCD(ClassDoc classDoc,
778 Configuration configuration) {
779 if (classDoc == null) {
780 return null;
781 }
782 ClassDoc supClassDoc = classDoc.superclass();
783 while (supClassDoc != null &&
784 (! (supClassDoc.isPublic() ||
785 isLinkable(supClassDoc, configuration))) ) {
786 supClassDoc = supClassDoc.superclass();
787 }
788 if (classDoc.equals(supClassDoc)) {
789 return null;
790 }
791 return supClassDoc;
792 }
794 /**
795 * Given a ClassDoc, return the name of its type (Class, Interface, etc.).
796 *
797 * @param cd the ClassDoc to check.
798 * @param lowerCaseOnly true if you want the name returned in lower case.
799 * If false, the first letter of the name is capatilized.
800 * @return
801 */
802 public static String getTypeName(Configuration config,
803 ClassDoc cd, boolean lowerCaseOnly) {
804 String typeName = "";
805 if (cd.isOrdinaryClass()) {
806 typeName = "doclet.Class";
807 } else if (cd.isInterface()) {
808 typeName = "doclet.Interface";
809 } else if (cd.isException()) {
810 typeName = "doclet.Exception";
811 } else if (cd.isError()) {
812 typeName = "doclet.Error";
813 } else if (cd.isAnnotationType()) {
814 typeName = "doclet.AnnotationType";
815 } else if (cd.isEnum()) {
816 typeName = "doclet.Enum";
817 }
818 return config.getText(
819 lowerCaseOnly ? typeName.toLowerCase() : typeName);
820 }
822 /**
823 * Given a string, replace all tabs with the appropriate
824 * number of spaces.
825 * @param tabLength the length of each tab.
826 * @param s the String to scan.
827 */
828 public static void replaceTabs(int tabLength, StringBuilder s) {
829 if (whitespace == null || whitespace.length() < tabLength)
830 whitespace = String.format("%" + tabLength + "s", " ");
831 int index = 0;
832 while ((index = s.indexOf("\t", index)) != -1) {
833 int spaceCount = tabLength - index % tabLength;
834 s.replace(index, index+1, whitespace.substring(0, spaceCount));
835 index += spaceCount;
836 }
837 }
838 private static String whitespace;
840 /**
841 * The documentation for values() and valueOf() in Enums are set by the
842 * doclet.
843 */
844 public static void setEnumDocumentation(Configuration configuration,
845 ClassDoc classDoc) {
846 MethodDoc[] methods = classDoc.methods();
847 for (int j = 0; j < methods.length; j++) {
848 MethodDoc currentMethod = methods[j];
849 if (currentMethod.name().equals("values") &&
850 currentMethod.parameters().length == 0) {
851 currentMethod.setRawCommentText(
852 configuration.getText("doclet.enum_values_doc", classDoc.name()));
853 } else if (currentMethod.name().equals("valueOf") &&
854 currentMethod.parameters().length == 1) {
855 Type paramType = currentMethod.parameters()[0].type();
856 if (paramType != null &&
857 paramType.qualifiedTypeName().equals(String.class.getName())) {
858 currentMethod.setRawCommentText(
859 configuration.getText("doclet.enum_valueof_doc"));
860 }
861 }
862 }
863 }
865 /**
866 * Return true if the given Doc is deprecated.
867 *
868 * @param doc the Doc to check.
869 * @return true if the given Doc is deprecated.
870 */
871 public static boolean isDeprecated(Doc doc) {
872 if (doc.tags("deprecated").length > 0) {
873 return true;
874 }
875 AnnotationDesc[] annotationDescList;
876 if (doc instanceof PackageDoc)
877 annotationDescList = ((PackageDoc)doc).annotations();
878 else
879 annotationDescList = ((ProgramElementDoc)doc).annotations();
880 for (int i = 0; i < annotationDescList.length; i++) {
881 if (annotationDescList[i].annotationType().qualifiedName().equals(
882 java.lang.Deprecated.class.getName())){
883 return true;
884 }
885 }
886 return false;
887 }
888 }