001    /*
002     * Copyright 1999-2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    
026    package com.sun.tools.doclets.internal.toolkit.util;
027    
028    import java.io.*;
029    import java.util.*;
030    
031    import com.sun.javadoc.*;
032    import com.sun.tools.doclets.internal.toolkit.*;
033    
034    /**
035     * Utilities Class for Doclets.
036     *
037     * This code is not part of an API.
038     * It is implementation that is subject to change.
039     * Do not use it as an API
040     *
041     * @author Atul M Dambalkar
042     * @author Jamie Ho
043     */
044    public class Util {
045    
046        /**
047         * A mapping between characters and their
048         * corresponding HTML escape character.
049         */
050        public static final String[][] HTML_ESCAPE_CHARS =
051        {{"&", "&amp;"}, {"<", "&lt;"}, {">", "&gt;"}};
052    
053        /**
054         * Return array of class members whose documentation is to be generated.
055         * If the member is deprecated do not include such a member in the
056         * returned array.
057         *
058         * @param  members             Array of members to choose from.
059         * @return ProgramElementDoc[] Array of eligible members for whom
060         *                             documentation is getting generated.
061         */
062        public static ProgramElementDoc[] excludeDeprecatedMembers(
063            ProgramElementDoc[] members) {
064            return
065                toProgramElementDocArray(excludeDeprecatedMembersAsList(members));
066        }
067    
068        /**
069         * Return array of class members whose documentation is to be generated.
070         * If the member is deprecated do not include such a member in the
071         * returned array.
072         *
073         * @param  members    Array of members to choose from.
074         * @return List       List of eligible members for whom
075         *                    documentation is getting generated.
076         */
077        public static List<ProgramElementDoc> excludeDeprecatedMembersAsList(
078            ProgramElementDoc[] members) {
079            List<ProgramElementDoc> list = new ArrayList<ProgramElementDoc>();
080            for (int i = 0; i < members.length; i++) {
081                if (members[i].tags("deprecated").length == 0) {
082                    list.add(members[i]);
083                }
084            }
085            Collections.sort(list);
086            return list;
087        }
088    
089        /**
090         * Return the list of ProgramElementDoc objects as Array.
091         */
092        public static ProgramElementDoc[] toProgramElementDocArray(List<ProgramElementDoc> list) {
093            ProgramElementDoc[] pgmarr = new ProgramElementDoc[list.size()];
094            for (int i = 0; i < list.size(); i++) {
095                pgmarr[i] = list.get(i);
096            }
097            return pgmarr;
098        }
099    
100        /**
101         * Return true if a non-public member found in the given array.
102         *
103         * @param  members Array of members to look into.
104         * @return boolean True if non-public member found, false otherwise.
105         */
106        public static boolean nonPublicMemberFound(ProgramElementDoc[] members) {
107            for (int i = 0; i < members.length; i++) {
108                if (!members[i].isPublic()) {
109                    return true;
110                }
111            }
112            return false;
113        }
114    
115        /**
116         * Search for the given method in the given class.
117         *
118         * @param  cd        Class to search into.
119         * @param  method    Method to be searched.
120         * @return MethodDoc Method found, null otherwise.
121         */
122        public static MethodDoc findMethod(ClassDoc cd, MethodDoc method) {
123            MethodDoc[] methods = cd.methods();
124            for (int i = 0; i < methods.length; i++) {
125                if (executableMembersEqual(method, methods[i])) {
126                    return methods[i];
127    
128                }
129            }
130            return null;
131        }
132    
133        /**
134         * @param member1 the first method to compare.
135         * @param member2 the second method to compare.
136         * @return true if member1 overrides/hides or is overriden/hidden by member2.
137         */
138        public static boolean executableMembersEqual(ExecutableMemberDoc member1,
139                ExecutableMemberDoc member2) {
140            if (! (member1 instanceof MethodDoc && member2 instanceof MethodDoc))
141                return false;
142    
143            MethodDoc method1 = (MethodDoc) member1;
144            MethodDoc method2 = (MethodDoc) member2;
145            if (method1.isStatic() && method2.isStatic()) {
146                Parameter[] targetParams = method1.parameters();
147                Parameter[] currentParams;
148                if (method1.name().equals(method2.name()) &&
149                       (currentParams = method2.parameters()).length ==
150                    targetParams.length) {
151                    int j;
152                    for (j = 0; j < targetParams.length; j++) {
153                        if (! (targetParams[j].typeName().equals(
154                                  currentParams[j].typeName()) ||
155                                       currentParams[j].type() instanceof TypeVariable ||
156                                       targetParams[j].type() instanceof TypeVariable)) {
157                            break;
158                        }
159                    }
160                    if (j == targetParams.length) {
161                        return true;
162                    }
163                }
164                return false;
165            } else {
166                    return method1.overrides(method2) ||
167                    method2.overrides(method1) ||
168                                    member1 == member2;
169            }
170        }
171    
172        /**
173         * According to the Java Language Specifications, all the outer classes
174         * and static inner classes are core classes.
175         */
176        public static boolean isCoreClass(ClassDoc cd) {
177            return cd.containingClass() == null || cd.isStatic();
178        }
179    
180        public static boolean matches(ProgramElementDoc doc1,
181                ProgramElementDoc doc2) {
182            if (doc1 instanceof ExecutableMemberDoc &&
183                doc2 instanceof ExecutableMemberDoc) {
184                ExecutableMemberDoc ed1 = (ExecutableMemberDoc)doc1;
185                ExecutableMemberDoc ed2 = (ExecutableMemberDoc)doc2;
186                return executableMembersEqual(ed1, ed2);
187            } else {
188                return doc1.name().equals(doc2.name());
189            }
190        }
191    
192        /**
193         * Copy source file to destination file.
194         *
195         * @throws SecurityException
196         * @throws IOException
197         */
198        public static void copyFile(File destfile, File srcfile)
199            throws IOException {
200            byte[] bytearr = new byte[512];
201            int len = 0;
202            FileInputStream input = new FileInputStream(srcfile);
203            File destDir = destfile.getParentFile();
204            destDir.mkdirs();
205            FileOutputStream output = new FileOutputStream(destfile);
206            try {
207                while ((len = input.read(bytearr)) != -1) {
208                    output.write(bytearr, 0, len);
209                }
210            } catch (FileNotFoundException exc) {
211            } catch (SecurityException exc) {
212            } finally {
213                input.close();
214                output.close();
215            }
216        }
217    
218        /**
219         * Copy the given directory contents from the source package directory
220         * to the generated documentation directory. For example for a package
221         * java.lang this method find out the source location of the package using
222         * {@link SourcePath} and if given directory is found in the source
223         * directory structure, copy the entire directory, to the generated
224         * documentation hierarchy.
225         *
226         * @param configuration The configuration of the current doclet.
227         * @param path The relative path to the directory to be copied.
228         * @param dir The original directory name to copy from.
229         * @param overwrite Overwrite files if true.
230         */
231        public static void copyDocFiles(Configuration configuration,
232                String path, String dir, boolean overwrite) {
233            if (checkCopyDocFilesErrors(configuration, path, dir)) {
234                return;
235            }
236            String destname = configuration.docFileDestDirName;
237            File srcdir = new File(path + dir);
238            if (destname.length() > 0 && !destname.endsWith(
239                   DirectoryManager.URL_FILE_SEPERATOR)) {
240                destname += DirectoryManager.URL_FILE_SEPERATOR;
241            }
242            String dest = destname + dir;
243            try {
244                File destdir = new File(dest);
245                DirectoryManager.createDirectory(configuration, dest);
246                String[] files = srcdir.list();
247                for (int i = 0; i < files.length; i++) {
248                    File srcfile = new File(srcdir, files[i]);
249                    File destfile = new File(destdir, files[i]);
250                    if (srcfile.isFile()) {
251                        if(destfile.exists() && ! overwrite) {
252                            configuration.message.warning((SourcePosition) null,
253                                    "doclet.Copy_Overwrite_warning",
254                                    srcfile.toString(), destdir.toString());
255                        } else {
256                            configuration.message.notice(
257                                "doclet.Copying_File_0_To_Dir_1",
258                                srcfile.toString(), destdir.toString());
259                            Util.copyFile(destfile, srcfile);
260                        }
261                    } else if(srcfile.isDirectory()) {
262                        if(configuration.copydocfilesubdirs
263                            && ! configuration.shouldExcludeDocFileDir(
264                              srcfile.getName())){
265                            copyDocFiles(configuration, path, dir +
266                                        DirectoryManager.URL_FILE_SEPERATOR + srcfile.getName(),
267                                    overwrite);
268                        }
269                    }
270                }
271            } catch (SecurityException exc) {
272                throw new DocletAbortException();
273            } catch (IOException exc) {
274                throw new DocletAbortException();
275            }
276        }
277    
278        /**
279         * Given the parameters for copying doc-files, check for errors.
280         *
281         * @param configuration The configuration of the current doclet.
282         * @param path The relative path to the directory to be copied.
283         * @param dirName The original directory name to copy from.
284         */
285        private static boolean checkCopyDocFilesErrors (Configuration configuration,
286                String path, String dirName) {
287            if ((configuration.sourcepath == null || configuration.sourcepath.length() == 0) &&
288                   (configuration.destDirName == null || configuration.destDirName.length() == 0)) {
289                //The destination path and source path are definitely equal.
290                return true;
291            }
292            File sourcePath, destPath = new File(configuration.destDirName);
293            StringTokenizer pathTokens = new StringTokenizer(
294                configuration.sourcepath == null ? "" : configuration.sourcepath,
295                File.pathSeparator);
296            //Check if the destination path is equal to the source path.  If yes,
297            //do not copy doc-file directories.
298            while(pathTokens.hasMoreTokens()){
299                sourcePath = new File(pathTokens.nextToken());
300                if(destPath.equals(sourcePath)){
301                    return true;
302                }
303            }
304            //Make sure the doc-file being copied exists.
305            File srcdir = new File(path + dirName);
306            if (! srcdir.exists()) {
307                return true;
308            }
309            return false;
310        }
311    
312        /**
313         * Copy a file in the resources directory to the destination
314         * directory (if it is not there already).  If
315         * <code>overwrite</code> is true and the destination file
316         * already exists, overwrite it.
317         *
318         * @param configuration  Holds the destination directory and error message
319         * @param resourcefile   The name of the resource file to copy
320         * @param overwrite      A flag to indicate whether the file in the
321         *                       destination directory will be overwritten if
322         *                       it already exists.
323         */
324        public static void copyResourceFile(Configuration configuration,
325                String resourcefile,
326                boolean overwrite) {
327            String destdir = configuration.destDirName;
328            String destresourcesdir = destdir + "resources";
329            DirectoryManager.createDirectory(configuration, destresourcesdir);
330            File destfile = new File(destresourcesdir, resourcefile);
331            if(destfile.exists() && (! overwrite)) return;
332            try {
333    
334                InputStream in = Configuration.class.getResourceAsStream(
335                    "resources/" + resourcefile);
336    
337                if(in==null) return;
338    
339                OutputStream out = new FileOutputStream(destfile);
340                byte[] buf = new byte[2048];
341                int n;
342                while((n = in.read(buf))>0) out.write(buf,0,n);
343    
344                in.close();
345                out.close();
346            } catch(Throwable t) {}
347        }
348    
349        /**
350         * Given a PackageDoc, return the source path for that package.
351         * @param configuration The Configuration for the current Doclet.
352         * @param pkgDoc The package to seach the path for.
353         * @return A string representing the path to the given package.
354         */
355        public static String getPackageSourcePath(Configuration configuration,
356                PackageDoc pkgDoc){
357            try{
358                String pkgPath = DirectoryManager.getDirectoryPath(pkgDoc);
359                String completePath = new SourcePath(configuration.sourcepath).
360                    getDirectory(pkgPath) + DirectoryManager.URL_FILE_SEPERATOR;
361                //Make sure that both paths are using the same seperators.
362                completePath = Util.replaceText(completePath, File.separator,
363                        DirectoryManager.URL_FILE_SEPERATOR);
364                pkgPath = Util.replaceText(pkgPath, File.separator,
365                        DirectoryManager.URL_FILE_SEPERATOR);
366                return completePath.substring(0, completePath.indexOf(pkgPath));
367            } catch (Exception e){
368                return "";
369            }
370        }
371    
372        /**
373         * We want the list of types in alphabetical order.  However, types are not
374         * comparable.  We need a comparator for now.
375         */
376        private static class TypeComparator implements Comparator<Type> {
377            public int compare(Type type1, Type type2) {
378                return type1.qualifiedTypeName().toLowerCase().compareTo(
379                    type2.qualifiedTypeName().toLowerCase());
380            }
381        }
382    
383        /**
384         * For the class return all implemented interfaces including the
385         * superinterfaces of the implementing interfaces, also iterate over for
386         * all the superclasses. For interface return all the extended interfaces
387         * as well as superinterfaces for those extended interfaces.
388         *
389         * @param  type       type whose implemented or
390         *                    super interfaces are sought.
391         * @param  configuration the current configuration of the doclet.
392         * @param  sort if true, return list of interfaces sorted alphabetically.
393         * @return List of all the required interfaces.
394         */
395        public static List<Type> getAllInterfaces(Type type,
396                Configuration configuration, boolean sort) {
397            Map<ClassDoc,Type> results = sort ? new TreeMap<ClassDoc,Type>() : new LinkedHashMap<ClassDoc,Type>();
398            Type[] interfaceTypes = null;
399            Type superType = null;
400            if (type instanceof ParameterizedType) {
401                interfaceTypes = ((ParameterizedType) type).interfaceTypes();
402                superType = ((ParameterizedType) type).superclassType();
403            } else if (type instanceof ClassDoc) {
404                interfaceTypes = ((ClassDoc) type).interfaceTypes();
405                superType = ((ClassDoc) type).superclassType();
406            } else {
407                interfaceTypes = type.asClassDoc().interfaceTypes();
408                superType = type.asClassDoc().superclassType();
409            }
410    
411            for (int i = 0; i < interfaceTypes.length; i++) {
412                Type interfaceType = interfaceTypes[i];
413                ClassDoc interfaceClassDoc = interfaceType.asClassDoc();
414                if (! (interfaceClassDoc.isPublic() ||
415                    (configuration == null ||
416                    isLinkable(interfaceClassDoc, configuration)))) {
417                    continue;
418                }
419                results.put(interfaceClassDoc, interfaceType);
420                List<Type> superInterfaces = getAllInterfaces(interfaceType, configuration, sort);
421                for (Iterator<Type> iter = superInterfaces.iterator(); iter.hasNext(); ) {
422                    Type t = iter.next();
423                    results.put(t.asClassDoc(), t);
424                }
425            }
426            if (superType == null)
427                return new ArrayList<Type>(results.values());
428            //Try walking the tree.
429            addAllInterfaceTypes(results,
430                superType,
431                superType instanceof ClassDoc ?
432                    ((ClassDoc) superType).interfaceTypes() :
433                    ((ParameterizedType) superType).interfaceTypes(),
434                false, configuration);
435            List<Type> resultsList = new ArrayList<Type>(results.values());
436            if (sort) {
437                    Collections.sort(resultsList, new TypeComparator());
438            }
439            return resultsList;
440        }
441    
442        public static List<Type> getAllInterfaces(Type type, Configuration configuration) {
443            return getAllInterfaces(type, configuration, true);
444        }
445    
446        private static void findAllInterfaceTypes(Map<ClassDoc,Type> results, ClassDoc c, boolean raw,
447                Configuration configuration) {
448            Type superType = c.superclassType();
449            if (superType == null)
450                return;
451            addAllInterfaceTypes(results, superType,
452                    superType instanceof ClassDoc ?
453                    ((ClassDoc) superType).interfaceTypes() :
454                    ((ParameterizedType) superType).interfaceTypes(),
455                    raw, configuration);
456        }
457    
458        private static void findAllInterfaceTypes(Map<ClassDoc,Type> results, ParameterizedType p,
459                Configuration configuration) {
460            Type superType = p.superclassType();
461            if (superType == null)
462                return;
463            addAllInterfaceTypes(results, superType,
464                    superType instanceof ClassDoc ?
465                    ((ClassDoc) superType).interfaceTypes() :
466                    ((ParameterizedType) superType).interfaceTypes(),
467                    false, configuration);
468        }
469    
470        private static void addAllInterfaceTypes(Map<ClassDoc,Type> results, Type type,
471                Type[] interfaceTypes, boolean raw,
472                Configuration configuration) {
473            for (int i = 0; i < interfaceTypes.length; i++) {
474                Type interfaceType = interfaceTypes[i];
475                ClassDoc interfaceClassDoc = interfaceType.asClassDoc();
476                if (! (interfaceClassDoc.isPublic() ||
477                    (configuration != null &&
478                    isLinkable(interfaceClassDoc, configuration)))) {
479                    continue;
480                }
481                if (raw)
482                    interfaceType = interfaceType.asClassDoc();
483                results.put(interfaceClassDoc, interfaceType);
484                List<Type> superInterfaces = getAllInterfaces(interfaceType, configuration);
485                for (Iterator<Type> iter = superInterfaces.iterator(); iter.hasNext(); ) {
486                    Type superInterface = iter.next();
487                    results.put(superInterface.asClassDoc(), superInterface);
488                }
489            }
490            if (type instanceof ParameterizedType)
491                findAllInterfaceTypes(results, (ParameterizedType) type, configuration);
492            else if (((ClassDoc) type).typeParameters().length == 0)
493                findAllInterfaceTypes(results, (ClassDoc) type, raw, configuration);
494            else
495                findAllInterfaceTypes(results, (ClassDoc) type, true, configuration);
496        }
497    
498    
499        public static <T extends ProgramElementDoc> List<T> asList(T[] members) {
500            List<T> list = new ArrayList<T>();
501            for (int i = 0; i < members.length; i++) {
502                list.add(members[i]);
503            }
504            return list;
505        }
506    
507        /**
508         * Enclose in quotes, used for paths and filenames that contains spaces
509         */
510        public static String quote(String filepath) {
511            return ("\"" + filepath + "\"");
512        }
513    
514        /**
515         * Given a package, return it's name.
516         * @param packageDoc the package to check.
517         * @return the name of the given package.
518         */
519        public static String getPackageName(PackageDoc packageDoc) {
520            return packageDoc == null || packageDoc.name().length() == 0 ?
521                DocletConstants.DEFAULT_PACKAGE_NAME : packageDoc.name();
522        }
523    
524        /**
525         * Given a package, return it's file name without the extension.
526         * @param packageDoc the package to check.
527         * @return the file name of the given package.
528         */
529        public static String getPackageFileHeadName(PackageDoc packageDoc) {
530            return packageDoc == null || packageDoc.name().length() == 0 ?
531                DocletConstants.DEFAULT_PACKAGE_FILE_NAME : packageDoc.name();
532        }
533    
534        /**
535         * Given a string, replace all occurraces of 'newStr' with 'oldStr'.
536         * @param originalStr the string to modify.
537         * @param oldStr the string to replace.
538         * @param newStr the string to insert in place of the old string.
539         */
540        public static String replaceText(String originalStr, String oldStr,
541                String newStr) {
542            if (oldStr == null || newStr == null || oldStr.equals(newStr)) {
543                return originalStr;
544            }
545            StringBuffer result = new StringBuffer(originalStr);
546            int startIndex = 0;
547            while ((startIndex = result.indexOf(oldStr, startIndex)) != -1) {
548                result = result.replace(startIndex, startIndex + oldStr.length(),
549                        newStr);
550                startIndex += newStr.length();
551            }
552            return result.toString();
553        }
554    
555        /**
556         * Given a string, escape all special html characters and
557         * return the result.
558         *
559         * @param s The string to check.
560         * @return the original string with all of the HTML characters
561         * escaped.
562         *
563         * @see #HTML_ESCAPE_CHARS
564         */
565        public static String escapeHtmlChars(String s) {
566            String result = s;
567            for (int i = 0; i < HTML_ESCAPE_CHARS.length; i++) {
568                result = Util.replaceText(result,
569                        HTML_ESCAPE_CHARS[i][0], HTML_ESCAPE_CHARS[i][1]);
570            }
571            return result;
572        }
573    
574        /**
575         * Create the directory path for the file to be generated, construct
576         * FileOutputStream and OutputStreamWriter depending upon docencoding.
577         *
578         * @param path The directory path to be created for this file.
579         * @param filename File Name to which the PrintWriter will do the Output.
580         * @param docencoding Encoding to be used for this file.
581         * @exception IOException Exception raised by the FileWriter is passed on
582         * to next level.
583         * @exception UnsupportedEncodingException Exception raised by the
584         * OutputStreamWriter is passed on to next level.
585         * @return Writer Writer for the file getting generated.
586         * @see java.io.FileOutputStream
587         * @see java.io.OutputStreamWriter
588         */
589        public static Writer genWriter(Configuration configuration,
590                String path, String filename,
591                String docencoding)
592            throws IOException, UnsupportedEncodingException {
593            FileOutputStream fos;
594            if (path != null) {
595                DirectoryManager.createDirectory(configuration, path);
596                fos = new FileOutputStream(((path.length() > 0)?
597                                                      path + File.separator: "") + filename);
598            } else {
599                fos = new FileOutputStream(filename);
600            }
601            if (docencoding == null) {
602                return new OutputStreamWriter(fos);
603            } else {
604                return new OutputStreamWriter(fos, docencoding);
605            }
606        }
607    
608        /**
609         * Given an annotation, return true if it should be documented and false
610         * otherwise.
611         *
612         * @param annotationDoc the annotation to check.
613         *
614         * @return true return true if it should be documented and false otherwise.
615         */
616        public static boolean isDocumentedAnnotation(AnnotationTypeDoc annotationDoc) {
617            AnnotationDesc[] annotationDescList = annotationDoc.annotations();
618            for (int i = 0; i < annotationDescList.length; i++) {
619                if (annotationDescList[i].annotationType().qualifiedName().equals(
620                       java.lang.annotation.Documented.class.getName())){
621                    return true;
622                }
623            }
624            return false;
625        }
626    
627        /**
628         * Given a string, return an array of tokens.  The separator can be escaped
629         * with the '\' character.  The '\' character may also be escaped by the
630         * '\' character.
631         *
632         * @param s         the string to tokenize.
633         * @param separator the separator char.
634         * @param maxTokens the maxmimum number of tokens returned.  If the
635         *                  max is reached, the remaining part of s is appended
636         *                  to the end of the last token.
637         *
638         * @return an array of tokens.
639         */
640        public static String[] tokenize(String s, char separator, int maxTokens) {
641            List<String> tokens = new ArrayList<String>();
642            StringBuilder  token = new StringBuilder ();
643            boolean prevIsEscapeChar = false;
644            for (int i = 0; i < s.length(); i += Character.charCount(i)) {
645                int currentChar = s.codePointAt(i);
646                if (prevIsEscapeChar) {
647                    // Case 1:  escaped character
648                    token.appendCodePoint(currentChar);
649                    prevIsEscapeChar = false;
650                } else if (currentChar == separator && tokens.size() < maxTokens-1) {
651                    // Case 2:  separator
652                    tokens.add(token.toString());
653                    token = new StringBuilder();
654                } else if (currentChar == '\\') {
655                    // Case 3:  escape character
656                    prevIsEscapeChar = true;
657                } else {
658                    // Case 4:  regular character
659                    token.appendCodePoint(currentChar);
660                }
661            }
662            if (token.length() > 0) {
663                tokens.add(token.toString());
664            }
665            return tokens.toArray(new String[] {});
666        }
667    
668        /**
669         * Return true if this class is linkable and false if we can't link to the
670         * desired class.
671         * <br>
672         * <b>NOTE:</b>  You can only link to external classes if they are public or
673         * protected.
674         *
675         * @param classDoc the class to check.
676         * @param configuration the current configuration of the doclet.
677         *
678         * @return true if this class is linkable and false if we can't link to the
679         * desired class.
680         */
681        public static boolean isLinkable(ClassDoc classDoc,
682                Configuration configuration) {
683            return
684                ((classDoc.isIncluded() && configuration.isGeneratedDoc(classDoc))) ||
685                (configuration.extern.isExternal(classDoc) &&
686                    (classDoc.isPublic() || classDoc.isProtected()));
687        }
688    
689        /**
690         * Given a class, return the closest visible super class.
691         *
692         * @param classDoc the class we are searching the parent for.
693         * @param configuration the current configuration of the doclet.
694         * @return the closest visible super class.  Return null if it cannot
695         *         be found (i.e. classDoc is java.lang.Object).
696         */
697        public static Type getFirstVisibleSuperClass(ClassDoc classDoc,
698                Configuration configuration) {
699            if (classDoc == null) {
700                return null;
701            }
702            Type sup = classDoc.superclassType();
703            ClassDoc supClassDoc = classDoc.superclass();
704            while (sup != null &&
705                      (! (supClassDoc.isPublic() ||
706                                  isLinkable(supClassDoc, configuration))) ) {
707                if (supClassDoc.superclass().qualifiedName().equals(supClassDoc.qualifiedName()))
708                    break;
709                sup = supClassDoc.superclassType();
710                supClassDoc = supClassDoc.superclass();
711            }
712            if (classDoc.equals(supClassDoc)) {
713                return null;
714            }
715            return sup;
716        }
717    
718        /**
719         * Given a class, return the closest visible super class.
720         *
721         * @param classDoc the class we are searching the parent for.
722         * @param configuration the current configuration of the doclet.
723         * @return the closest visible super class.  Return null if it cannot
724         *         be found (i.e. classDoc is java.lang.Object).
725         */
726        public static ClassDoc getFirstVisibleSuperClassCD(ClassDoc classDoc,
727                Configuration configuration) {
728            if (classDoc == null) {
729                return null;
730            }
731            ClassDoc supClassDoc = classDoc.superclass();
732            while (supClassDoc != null &&
733                      (! (supClassDoc.isPublic() ||
734                                  isLinkable(supClassDoc, configuration))) ) {
735                supClassDoc = supClassDoc.superclass();
736            }
737            if (classDoc.equals(supClassDoc)) {
738                return null;
739            }
740            return supClassDoc;
741        }
742    
743        /**
744         * Given a ClassDoc, return the name of its type (Class, Interface, etc.).
745         *
746         * @param cd the ClassDoc to check.
747         * @param lowerCaseOnly true if you want the name returned in lower case.
748         *                      If false, the first letter of the name is capatilized.
749         * @return
750         */
751        public static String getTypeName(Configuration config,
752            ClassDoc cd, boolean lowerCaseOnly) {
753            String typeName = "";
754            if (cd.isOrdinaryClass()) {
755                typeName = "doclet.Class";
756            } else if (cd.isInterface()) {
757                typeName = "doclet.Interface";
758            } else if (cd.isException()) {
759                typeName = "doclet.Exception";
760            } else if (cd.isError()) {
761                typeName = "doclet.Error";
762            } else if (cd.isAnnotationType()) {
763                typeName = "doclet.AnnotationType";
764            } else if (cd.isEnum()) {
765                typeName = "doclet.Enum";
766            }
767            return config.getText(
768                lowerCaseOnly ? typeName.toLowerCase() : typeName);
769        }
770    
771        /**
772         * Given a string, replace all tabs with the appropriate
773         * number of spaces.
774         * @param tabLength the length of each tab.
775         * @param s the String to scan.
776         */
777        public static void replaceTabs(int tabLength, StringBuffer s) {
778            int index, col;
779            StringBuffer whitespace;
780            while ((index = s.indexOf("\t")) != -1) {
781                whitespace = new StringBuffer();
782                col = index;
783                do {
784                    whitespace.append(" ");
785                    col++;
786                } while ((col%tabLength) != 0);
787                s.replace(index, index+1, whitespace.toString());
788            }
789        }
790    
791        /**
792         * The documentation for values() and valueOf() in Enums are set by the
793         * doclet.
794         */
795        public static void setEnumDocumentation(Configuration configuration,
796                ClassDoc classDoc) {
797            MethodDoc[] methods = classDoc.methods();
798            for (int j = 0; j < methods.length; j++) {
799                MethodDoc currentMethod = methods[j];
800                if (currentMethod.name().equals("values") &&
801                    currentMethod.parameters().length == 0) {
802                    currentMethod.setRawCommentText(
803                        configuration.getText("doclet.enum_values_doc", classDoc.name()));
804                } else if (currentMethod.name().equals("valueOf") &&
805                    currentMethod.parameters().length == 1) {
806                    Type paramType = currentMethod.parameters()[0].type();
807                    if (paramType != null &&
808                        paramType.qualifiedTypeName().equals(String.class.getName())) {
809                        currentMethod.setRawCommentText(
810                            configuration.getText("doclet.enum_valueof_doc"));
811                    }
812                }
813            }
814        }
815    
816        /**
817         *  Return true if the given Doc is deprecated.
818         *
819         * @param doc the Doc to check.
820         * @return true if the given Doc is deprecated.
821         */
822        public static boolean isDeprecated(ProgramElementDoc doc) {
823            if (doc.tags("deprecated").length > 0) {
824                return true;
825            }
826            AnnotationDesc[] annotationDescList = doc.annotations();
827            for (int i = 0; i < annotationDescList.length; i++) {
828                if (annotationDescList[i].annotationType().qualifiedName().equals(
829                       java.lang.Deprecated.class.getName())){
830                    return true;
831                }
832            }
833            return false;
834        }
835    }