Utils.java revision 2571:10fc81ac75b4
1/*
2 * Copyright (c) 1999, 2014, 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
26package com.sun.tools.doclets.internal.toolkit.util;
27
28import java.io.*;
29import java.lang.annotation.Documented;
30import java.lang.annotation.ElementType;
31import java.lang.annotation.Target;
32import java.text.Collator;
33import java.util.*;
34
35import javax.tools.StandardLocation;
36
37import com.sun.javadoc.*;
38import com.sun.javadoc.AnnotationDesc.ElementValuePair;
39import com.sun.tools.doclets.internal.toolkit.*;
40import com.sun.tools.javac.util.StringUtils;
41
42/**
43 * Utilities Class for Doclets.
44 *
45 *  <p><b>This is NOT part of any supported API.
46 *  If you write code that depends on this, you do so at your own risk.
47 *  This code and its internal interfaces are subject to change or
48 *  deletion without notice.</b>
49 *
50 * @author Atul M Dambalkar
51 * @author Jamie Ho
52 */
53public class Utils {
54    /**
55     * Return array of class members whose documentation is to be generated.
56     * If the member is deprecated do not include such a member in the
57     * returned array.
58     *
59     * @param  members             Array of members to choose from.
60     * @return ProgramElementDoc[] Array of eligible members for whom
61     *                             documentation is getting generated.
62     */
63    public ProgramElementDoc[] excludeDeprecatedMembers(
64        ProgramElementDoc[] members) {
65        return
66            toProgramElementDocArray(excludeDeprecatedMembersAsList(members));
67    }
68
69    /**
70     * Return array of class members whose documentation is to be generated.
71     * If the member is deprecated do not include such a member in the
72     * returned array.
73     *
74     * @param  members    Array of members to choose from.
75     * @return List       List of eligible members for whom
76     *                    documentation is getting generated.
77     */
78    public List<ProgramElementDoc> excludeDeprecatedMembersAsList(
79        ProgramElementDoc[] members) {
80        List<ProgramElementDoc> list = new ArrayList<>();
81        for (ProgramElementDoc member : members) {
82            if (member.tags("deprecated").length == 0) {
83                list.add(member);
84            }
85        }
86        Collections.sort(list);
87        return list;
88    }
89
90    /**
91     * Return the list of ProgramElementDoc objects as Array.
92     */
93    public ProgramElementDoc[] toProgramElementDocArray(List<ProgramElementDoc> list) {
94        ProgramElementDoc[] pgmarr = new ProgramElementDoc[list.size()];
95        for (int i = 0; i < list.size(); i++) {
96            pgmarr[i] = list.get(i);
97        }
98        return pgmarr;
99    }
100
101    /**
102     * Return true if a non-public member found in the given array.
103     *
104     * @param  members Array of members to look into.
105     * @return boolean True if non-public member found, false otherwise.
106     */
107    public boolean nonPublicMemberFound(ProgramElementDoc[] members) {
108        for (ProgramElementDoc member : members) {
109            if (!member.isPublic()) {
110                return true;
111            }
112        }
113        return false;
114    }
115
116    /**
117     * Search for the given method in the given class.
118     *
119     * @param  cd        Class to search into.
120     * @param  method    Method to be searched.
121     * @return MethodDoc Method found, null otherwise.
122     */
123    public MethodDoc findMethod(ClassDoc cd, MethodDoc method) {
124        MethodDoc[] methods = cd.methods();
125        for (MethodDoc m : methods) {
126            if (executableMembersEqual(method, m)) {
127                return m;
128
129            }
130        }
131        return null;
132    }
133
134    /**
135     * @param member1 the first method to compare.
136     * @param member2 the second method to compare.
137     * @return true if member1 overrides/hides or is overriden/hidden by member2.
138     */
139    public boolean executableMembersEqual(ExecutableMemberDoc member1,
140            ExecutableMemberDoc member2) {
141        if (! (member1 instanceof MethodDoc && member2 instanceof MethodDoc))
142            return false;
143
144        MethodDoc method1 = (MethodDoc) member1;
145        MethodDoc method2 = (MethodDoc) member2;
146        if (method1.isStatic() && method2.isStatic()) {
147            Parameter[] targetParams = method1.parameters();
148            Parameter[] currentParams;
149            if (method1.name().equals(method2.name()) &&
150                   (currentParams = method2.parameters()).length ==
151                targetParams.length) {
152                int j;
153                for (j = 0; j < targetParams.length; j++) {
154                    if (! (targetParams[j].typeName().equals(
155                              currentParams[j].typeName()) ||
156                                   currentParams[j].type() instanceof TypeVariable ||
157                                   targetParams[j].type() instanceof TypeVariable)) {
158                        break;
159                    }
160                }
161                if (j == targetParams.length) {
162                    return true;
163                }
164            }
165            return false;
166        } else {
167                return method1.overrides(method2) ||
168                method2.overrides(method1) ||
169                                member1 == member2;
170        }
171    }
172
173    /**
174     * According to
175     * <cite>The Java&trade; Language Specification</cite>,
176     * all the outer classes and static inner classes are core classes.
177     */
178    public boolean isCoreClass(ClassDoc cd) {
179        return cd.containingClass() == null || cd.isStatic();
180    }
181
182    public boolean matches(ProgramElementDoc doc1,
183            ProgramElementDoc doc2) {
184        if (doc1 instanceof ExecutableMemberDoc &&
185            doc2 instanceof ExecutableMemberDoc) {
186            ExecutableMemberDoc ed1 = (ExecutableMemberDoc)doc1;
187            ExecutableMemberDoc ed2 = (ExecutableMemberDoc)doc2;
188            return executableMembersEqual(ed1, ed2);
189        } else {
190            return doc1.name().equals(doc2.name());
191        }
192    }
193
194    /**
195     * Copy the given directory contents from the source package directory
196     * to the generated documentation directory. For example for a package
197     * java.lang this method find out the source location of the package using
198     * {@link SourcePath} and if given directory is found in the source
199     * directory structure, copy the entire directory, to the generated
200     * documentation hierarchy.
201     *
202     * @param configuration The configuration of the current doclet.
203     * @param path The relative path to the directory to be copied.
204     * @param dir The original directory name to copy from.
205     * @param overwrite Overwrite files if true.
206     */
207    public void copyDocFiles(Configuration configuration, PackageDoc pd) {
208        copyDocFiles(configuration, DocPath.forPackage(pd).resolve(DocPaths.DOC_FILES));
209    }
210
211    public void copyDocFiles(Configuration configuration, DocPath dir) {
212        try {
213            boolean first = true;
214            for (DocFile f : DocFile.list(configuration, StandardLocation.SOURCE_PATH, dir)) {
215                if (!f.isDirectory()) {
216                    continue;
217                }
218                DocFile srcdir = f;
219                DocFile destdir = DocFile.createFileForOutput(configuration, dir);
220                if (srcdir.isSameFile(destdir)) {
221                    continue;
222                }
223
224                for (DocFile srcfile: srcdir.list()) {
225                    DocFile destfile = destdir.resolve(srcfile.getName());
226                    if (srcfile.isFile()) {
227                        if (destfile.exists() && !first) {
228                            configuration.message.warning((SourcePosition) null,
229                                    "doclet.Copy_Overwrite_warning",
230                                    srcfile.getPath(), destdir.getPath());
231                        } else {
232                            configuration.message.notice(
233                                    "doclet.Copying_File_0_To_Dir_1",
234                                    srcfile.getPath(), destdir.getPath());
235                            destfile.copyFile(srcfile);
236                        }
237                    } else if (srcfile.isDirectory()) {
238                        if (configuration.copydocfilesubdirs
239                                && !configuration.shouldExcludeDocFileDir(srcfile.getName())) {
240                            copyDocFiles(configuration, dir.resolve(srcfile.getName()));
241                        }
242                    }
243                }
244
245                first = false;
246            }
247        } catch (SecurityException | IOException exc) {
248            throw new DocletAbortException(exc);
249        }
250    }
251
252    /**
253     * We want the list of types in alphabetical order.  However, types are not
254     * comparable.  We need a comparator for now.
255     */
256    private static class TypeComparator implements Comparator<Type> {
257        public int compare(Type type1, Type type2) {
258            return type1.qualifiedTypeName().compareToIgnoreCase(
259                type2.qualifiedTypeName());
260        }
261    }
262
263    /**
264     * For the class return all implemented interfaces including the
265     * superinterfaces of the implementing interfaces, also iterate over for
266     * all the superclasses. For interface return all the extended interfaces
267     * as well as superinterfaces for those extended interfaces.
268     *
269     * @param  type       type whose implemented or
270     *                    super interfaces are sought.
271     * @param  configuration the current configuration of the doclet.
272     * @param  sort if true, return list of interfaces sorted alphabetically.
273     * @return List of all the required interfaces.
274     */
275    public List<Type> getAllInterfaces(Type type,
276            Configuration configuration, boolean sort) {
277        Map<ClassDoc,Type> results = sort ?
278                new TreeMap<ClassDoc,Type>() :
279                new LinkedHashMap<ClassDoc,Type>();
280        Type[] interfaceTypes = null;
281        Type superType = null;
282        if (type instanceof ParameterizedType) {
283            interfaceTypes = ((ParameterizedType) type).interfaceTypes();
284            superType = ((ParameterizedType) type).superclassType();
285        } else if (type instanceof ClassDoc) {
286            interfaceTypes = ((ClassDoc) type).interfaceTypes();
287            superType = ((ClassDoc) type).superclassType();
288        } else {
289            interfaceTypes = type.asClassDoc().interfaceTypes();
290            superType = type.asClassDoc().superclassType();
291        }
292
293        for (Type interfaceType : interfaceTypes) {
294            ClassDoc interfaceClassDoc = interfaceType.asClassDoc();
295            if (!(interfaceClassDoc.isPublic() ||
296                  (configuration == null ||
297                   isLinkable(interfaceClassDoc, configuration)))) {
298                continue;
299            }
300            results.put(interfaceClassDoc, interfaceType);
301            for (Type t : getAllInterfaces(interfaceType, configuration, sort)) {
302                results.put(t.asClassDoc(), t);
303            }
304        }
305        if (superType == null)
306            return new ArrayList<>(results.values());
307        //Try walking the tree.
308        addAllInterfaceTypes(results,
309            superType,
310            interfaceTypesOf(superType),
311            false, configuration);
312        List<Type> resultsList = new ArrayList<>(results.values());
313        if (sort) {
314                Collections.sort(resultsList, new TypeComparator());
315        }
316        return resultsList;
317    }
318
319    private Type[] interfaceTypesOf(Type type) {
320        if (type instanceof AnnotatedType)
321            type = ((AnnotatedType)type).underlyingType();
322        return type instanceof ClassDoc ?
323                ((ClassDoc)type).interfaceTypes() :
324                ((ParameterizedType)type).interfaceTypes();
325    }
326
327    public List<Type> getAllInterfaces(Type type, Configuration configuration) {
328        return getAllInterfaces(type, configuration, true);
329    }
330
331    private void findAllInterfaceTypes(Map<ClassDoc,Type> results, ClassDoc c, boolean raw,
332            Configuration configuration) {
333        Type superType = c.superclassType();
334        if (superType == null)
335            return;
336        addAllInterfaceTypes(results, superType,
337                interfaceTypesOf(superType),
338                raw, configuration);
339    }
340
341    private void findAllInterfaceTypes(Map<ClassDoc,Type> results, ParameterizedType p,
342            Configuration configuration) {
343        Type superType = p.superclassType();
344        if (superType == null)
345            return;
346        addAllInterfaceTypes(results, superType,
347                interfaceTypesOf(superType),
348                false, configuration);
349    }
350
351    private void addAllInterfaceTypes(Map<ClassDoc,Type> results, Type type,
352            Type[] interfaceTypes, boolean raw,
353            Configuration configuration) {
354        for (Type interfaceType : interfaceTypes) {
355            ClassDoc interfaceClassDoc = interfaceType.asClassDoc();
356            if (!(interfaceClassDoc.isPublic() ||
357                  (configuration != null &&
358                   isLinkable(interfaceClassDoc, configuration)))) {
359                continue;
360            }
361            if (raw)
362                interfaceType = interfaceType.asClassDoc();
363            results.put(interfaceClassDoc, interfaceType);
364            List<Type> superInterfaces = getAllInterfaces(interfaceType, configuration);
365            for (Type superInterface : superInterfaces) {
366                results.put(superInterface.asClassDoc(), superInterface);
367            }
368        }
369        if (type instanceof AnnotatedType)
370            type = ((AnnotatedType)type).underlyingType();
371
372        if (type instanceof ParameterizedType)
373            findAllInterfaceTypes(results, (ParameterizedType) type, configuration);
374        else if (((ClassDoc) type).typeParameters().length == 0)
375            findAllInterfaceTypes(results, (ClassDoc) type, raw, configuration);
376        else
377            findAllInterfaceTypes(results, (ClassDoc) type, true, configuration);
378    }
379
380    /**
381     * Enclose in quotes, used for paths and filenames that contains spaces
382     */
383    public String quote(String filepath) {
384        return ("\"" + filepath + "\"");
385    }
386
387    /**
388     * Given a package, return its name.
389     * @param packageDoc the package to check.
390     * @return the name of the given package.
391     */
392    public String getPackageName(PackageDoc packageDoc) {
393        return packageDoc == null || packageDoc.name().length() == 0 ?
394            DocletConstants.DEFAULT_PACKAGE_NAME : packageDoc.name();
395    }
396
397    /**
398     * Given a package, return its file name without the extension.
399     * @param packageDoc the package to check.
400     * @return the file name of the given package.
401     */
402    public String getPackageFileHeadName(PackageDoc packageDoc) {
403        return packageDoc == null || packageDoc.name().length() == 0 ?
404            DocletConstants.DEFAULT_PACKAGE_FILE_NAME : packageDoc.name();
405    }
406
407    /**
408     * Given a string, replace all occurrences of 'newStr' with 'oldStr'.
409     * @param originalStr the string to modify.
410     * @param oldStr the string to replace.
411     * @param newStr the string to insert in place of the old string.
412     */
413    public String replaceText(String originalStr, String oldStr,
414            String newStr) {
415        if (oldStr == null || newStr == null || oldStr.equals(newStr)) {
416            return originalStr;
417        }
418        return originalStr.replace(oldStr, newStr);
419    }
420
421    /**
422     * Given an annotation, return true if it should be documented and false
423     * otherwise.
424     *
425     * @param annotationDoc the annotation to check.
426     *
427     * @return true return true if it should be documented and false otherwise.
428     */
429    public boolean isDocumentedAnnotation(AnnotationTypeDoc annotationDoc) {
430        for (AnnotationDesc anno : annotationDoc.annotations()) {
431            if (anno.annotationType().qualifiedName().equals(
432                    Documented.class.getName())) {
433                return true;
434            }
435        }
436        return false;
437    }
438
439    private boolean isDeclarationTarget(AnnotationDesc targetAnno) {
440        // The error recovery steps here are analogous to TypeAnnotations
441        ElementValuePair[] elems = targetAnno.elementValues();
442        if (elems == null
443            || elems.length != 1
444            || !"value".equals(elems[0].element().name())
445            || !(elems[0].value().value() instanceof AnnotationValue[]))
446            return true;    // error recovery
447
448        for (AnnotationValue aValue : (AnnotationValue[])elems[0].value().value()) {
449            Object value = aValue.value();
450            if (!(value instanceof FieldDoc))
451                return true; // error recovery
452
453            FieldDoc eValue = (FieldDoc) value;
454            if (isJava5DeclarationElementType(eValue)) {
455                return true;
456            }
457        }
458
459        return false;
460    }
461
462    /**
463     * Returns true if the {@code annotationDoc} is to be treated
464     * as a declaration annotation, when targeting the
465     * {@code elemType} element type.
466     *
467     * @param annotationDoc the annotationDoc to check
468     * @param elemType  the targeted elemType
469     * @return true if annotationDoc is a declaration annotation
470     */
471    public boolean isDeclarationAnnotation(AnnotationTypeDoc annotationDoc,
472            boolean isJava5DeclarationLocation) {
473        if (!isJava5DeclarationLocation)
474            return false;
475        AnnotationDesc[] annotationDescList = annotationDoc.annotations();
476        // Annotations with no target are treated as declaration as well
477        if (annotationDescList.length==0)
478            return true;
479        for (AnnotationDesc anno : annotationDescList) {
480            if (anno.annotationType().qualifiedName().equals(
481                    Target.class.getName())) {
482                if (isDeclarationTarget(anno))
483                    return true;
484            }
485        }
486        return false;
487    }
488
489    /**
490     * Return true if this class is linkable and false if we can't link to the
491     * desired class.
492     * <br>
493     * <b>NOTE:</b>  You can only link to external classes if they are public or
494     * protected.
495     *
496     * @param classDoc the class to check.
497     * @param configuration the current configuration of the doclet.
498     *
499     * @return true if this class is linkable and false if we can't link to the
500     * desired class.
501     */
502    public boolean isLinkable(ClassDoc classDoc,
503            Configuration configuration) {
504        return
505            ((classDoc.isIncluded() && configuration.isGeneratedDoc(classDoc))) ||
506            (configuration.extern.isExternal(classDoc) &&
507                (classDoc.isPublic() || classDoc.isProtected()));
508    }
509
510    /**
511     * Given a class, return the closest visible super class.
512     *
513     * @param classDoc the class we are searching the parent for.
514     * @param configuration the current configuration of the doclet.
515     * @return the closest visible super class.  Return null if it cannot
516     *         be found (i.e. classDoc is java.lang.Object).
517     */
518    public Type getFirstVisibleSuperClass(ClassDoc classDoc,
519            Configuration configuration) {
520        if (classDoc == null) {
521            return null;
522        }
523        Type sup = classDoc.superclassType();
524        ClassDoc supClassDoc = classDoc.superclass();
525        while (sup != null &&
526                  (! (supClassDoc.isPublic() ||
527                              isLinkable(supClassDoc, configuration))) ) {
528            if (supClassDoc.superclass().qualifiedName().equals(supClassDoc.qualifiedName()))
529                break;
530            sup = supClassDoc.superclassType();
531            supClassDoc = supClassDoc.superclass();
532        }
533        if (classDoc.equals(supClassDoc)) {
534            return null;
535        }
536        return sup;
537    }
538
539    /**
540     * Given a class, return the closest visible super class.
541     *
542     * @param classDoc the class we are searching the parent for.
543     * @param configuration the current configuration of the doclet.
544     * @return the closest visible super class.  Return null if it cannot
545     *         be found (i.e. classDoc is java.lang.Object).
546     */
547    public ClassDoc getFirstVisibleSuperClassCD(ClassDoc classDoc,
548            Configuration configuration) {
549        if (classDoc == null) {
550            return null;
551        }
552        ClassDoc supClassDoc = classDoc.superclass();
553        while (supClassDoc != null &&
554                  (! (supClassDoc.isPublic() ||
555                              isLinkable(supClassDoc, configuration))) ) {
556            supClassDoc = supClassDoc.superclass();
557        }
558        if (classDoc.equals(supClassDoc)) {
559            return null;
560        }
561        return supClassDoc;
562    }
563
564    /**
565     * Given a ClassDoc, return the name of its type (Class, Interface, etc.).
566     *
567     * @param cd the ClassDoc to check.
568     * @param lowerCaseOnly true if you want the name returned in lower case.
569     *                      If false, the first letter of the name is capitalized.
570     * @return
571     */
572    public String getTypeName(Configuration config,
573        ClassDoc cd, boolean lowerCaseOnly) {
574        String typeName = "";
575        if (cd.isOrdinaryClass()) {
576            typeName = "doclet.Class";
577        } else if (cd.isInterface()) {
578            typeName = "doclet.Interface";
579        } else if (cd.isException()) {
580            typeName = "doclet.Exception";
581        } else if (cd.isError()) {
582            typeName = "doclet.Error";
583        } else if (cd.isAnnotationType()) {
584            typeName = "doclet.AnnotationType";
585        } else if (cd.isEnum()) {
586            typeName = "doclet.Enum";
587        }
588        return config.getText(
589            lowerCaseOnly ? StringUtils.toLowerCase(typeName) : typeName);
590    }
591
592    /**
593     * Replace all tabs in a string with the appropriate number of spaces.
594     * The string may be a multi-line string.
595     * @param configuration the doclet configuration defining the setting for the
596     *                      tab length.
597     * @param text the text for which the tabs should be expanded
598     * @return the text with all tabs expanded
599     */
600    public String replaceTabs(Configuration configuration, String text) {
601        if (!text.contains("\t"))
602            return text;
603
604        final int tabLength = configuration.sourcetab;
605        final String whitespace = configuration.tabSpaces;
606        final int textLength = text.length();
607        StringBuilder result = new StringBuilder(textLength);
608        int pos = 0;
609        int lineLength = 0;
610        for (int i = 0; i < textLength; i++) {
611            char ch = text.charAt(i);
612            switch (ch) {
613                case '\n': case '\r':
614                    lineLength = 0;
615                    break;
616                case '\t':
617                    result.append(text, pos, i);
618                    int spaceCount = tabLength - lineLength % tabLength;
619                    result.append(whitespace, 0, spaceCount);
620                    lineLength += spaceCount;
621                    pos = i + 1;
622                    break;
623                default:
624                    lineLength++;
625            }
626        }
627        result.append(text, pos, textLength);
628        return result.toString();
629    }
630
631    public String normalizeNewlines(String text) {
632        StringBuilder sb = new StringBuilder();
633        final int textLength = text.length();
634        final String NL = DocletConstants.NL;
635        int pos = 0;
636        for (int i = 0; i < textLength; i++) {
637            char ch = text.charAt(i);
638            switch (ch) {
639                case '\n':
640                    sb.append(text, pos, i);
641                    sb.append(NL);
642                    pos = i + 1;
643                    break;
644                case '\r':
645                    sb.append(text, pos, i);
646                    sb.append(NL);
647                    if (i + 1 < textLength && text.charAt(i + 1) == '\n')
648                        i++;
649                    pos = i + 1;
650                    break;
651            }
652        }
653        sb.append(text, pos, textLength);
654        return sb.toString();
655    }
656
657    /**
658     * The documentation for values() and valueOf() in Enums are set by the
659     * doclet.
660     */
661    public void setEnumDocumentation(Configuration configuration,
662            ClassDoc classDoc) {
663        for (MethodDoc currentMethod : classDoc.methods()) {
664            if (currentMethod.name().equals("values") &&
665                currentMethod.parameters().length == 0) {
666                StringBuilder sb = new StringBuilder();
667                sb.append(configuration.getText("doclet.enum_values_doc.main", classDoc.name()));
668                sb.append("\n@return ");
669                sb.append(configuration.getText("doclet.enum_values_doc.return"));
670                currentMethod.setRawCommentText(sb.toString());
671            } else if (currentMethod.name().equals("valueOf") &&
672                     currentMethod.parameters().length == 1) {
673                Type paramType = currentMethod.parameters()[0].type();
674                if (paramType != null &&
675                    paramType.qualifiedTypeName().equals(String.class.getName())) {
676                    StringBuilder sb = new StringBuilder();
677                    sb.append(configuration.getText("doclet.enum_valueof_doc.main", classDoc.name()));
678                    sb.append("\n@param name ");
679                    sb.append(configuration.getText("doclet.enum_valueof_doc.param_name"));
680                    sb.append("\n@return ");
681                    sb.append(configuration.getText("doclet.enum_valueof_doc.return"));
682                    sb.append("\n@throws IllegalArgumentException ");
683                    sb.append(configuration.getText("doclet.enum_valueof_doc.throws_ila"));
684                    sb.append("\n@throws NullPointerException ");
685                    sb.append(configuration.getText("doclet.enum_valueof_doc.throws_npe"));
686                    currentMethod.setRawCommentText(sb.toString());
687                }
688            }
689        }
690    }
691
692    /**
693     *  Return true if the given Doc is deprecated.
694     *
695     * @param doc the Doc to check.
696     * @return true if the given Doc is deprecated.
697     */
698    public boolean isDeprecated(Doc doc) {
699        if (doc.tags("deprecated").length > 0) {
700            return true;
701        }
702        AnnotationDesc[] annotationDescList;
703        if (doc instanceof PackageDoc)
704            annotationDescList = ((PackageDoc)doc).annotations();
705        else
706            annotationDescList = ((ProgramElementDoc)doc).annotations();
707        for (AnnotationDesc anno : annotationDescList) {
708            if (anno.annotationType().qualifiedName().equals(
709                    Deprecated.class.getName())) {
710                return true;
711            }
712        }
713        return false;
714    }
715
716    /**
717     * A convenience method to get property name from the name of the
718     * getter or setter method.
719     * @param name name of the getter or setter method.
720     * @return the name of the property of the given setter of getter.
721     */
722    public String propertyNameFromMethodName(Configuration configuration, String name) {
723        String propertyName = null;
724        if (name.startsWith("get") || name.startsWith("set")) {
725            propertyName = name.substring(3);
726        } else if (name.startsWith("is")) {
727            propertyName = name.substring(2);
728        }
729        if ((propertyName == null) || propertyName.isEmpty()){
730            return "";
731        }
732        return propertyName.substring(0, 1).toLowerCase(configuration.getLocale())
733                + propertyName.substring(1);
734    }
735
736    /**
737     * In case of JavaFX mode on, filters out classes that are private,
738     * package private or having the @treatAsPrivate annotation. Those are not
739     * documented in JavaFX mode.
740     *
741     * @param classes array of classes to be filtered.
742     * @param javafx set to true if in JavaFX mode.
743     * @return list of filtered classes.
744     */
745    public ClassDoc[] filterOutPrivateClasses(final ClassDoc[] classes,
746                                                     boolean javafx) {
747        if (!javafx) {
748            return classes;
749        }
750        final List<ClassDoc> filteredOutClasses = new ArrayList<>(classes.length);
751        for (ClassDoc classDoc : classes) {
752            if (classDoc.isPrivate() || classDoc.isPackagePrivate()) {
753                continue;
754            }
755            Tag[] aspTags = classDoc.tags("treatAsPrivate");
756            if (aspTags != null && aspTags.length > 0) {
757                continue;
758            }
759            filteredOutClasses.add(classDoc);
760        }
761
762        return filteredOutClasses.toArray(new ClassDoc[0]);
763    }
764
765    /**
766     * Test whether the given FieldDoc is one of the declaration annotation ElementTypes
767     * defined in Java 5.
768     * Instead of testing for one of the new enum constants added in Java 8, test for
769     * the old constants. This prevents bootstrapping problems.
770     *
771     * @param elt The FieldDoc to test
772     * @return true, iff the given ElementType is one of the constants defined in Java 5
773     * @since 1.8
774     */
775    public boolean isJava5DeclarationElementType(FieldDoc elt) {
776        return elt.name().contentEquals(ElementType.ANNOTATION_TYPE.name()) ||
777                elt.name().contentEquals(ElementType.CONSTRUCTOR.name()) ||
778                elt.name().contentEquals(ElementType.FIELD.name()) ||
779                elt.name().contentEquals(ElementType.LOCAL_VARIABLE.name()) ||
780                elt.name().contentEquals(ElementType.METHOD.name()) ||
781                elt.name().contentEquals(ElementType.PACKAGE.name()) ||
782                elt.name().contentEquals(ElementType.PARAMETER.name()) ||
783                elt.name().contentEquals(ElementType.TYPE.name());
784    }
785
786    /**
787     * A general purpose case insensitive String comparator, which compares two Strings using a Collator
788     * strength of "TERTIARY".
789     *
790     * @param s1 first String to compare.
791     * @param s2 second String to compare.
792     * @return a negative integer, zero, or a positive integer as the first
793     *         argument is less than, equal to, or greater than the second.
794     */
795    public static int compareStrings(String s1, String s2) {
796        return compareStrings(true, s1, s2);
797    }
798    /**
799     * A general purpose case sensitive String comparator, which compares two Strings using a Collator
800     * strength of "SECONDARY".
801     *
802     * @param s1 first String to compare.
803     * @param s2 second String to compare.
804     * @return a negative integer, zero, or a positive integer as the first
805     *         argument is less than, equal to, or greater than the second.
806     */
807    public static int compareCaseCompare(String s1, String s2) {
808        return compareStrings(false, s1, s2);
809    }
810    private static int compareStrings(boolean caseSensitive, String s1, String s2) {
811        Collator collator = Collator.getInstance();
812        collator.setStrength(caseSensitive ? Collator.TERTIARY : Collator.SECONDARY);
813        return collator.compare(s1, s2);
814    }
815
816    /**
817     * A comparator for index file presentations, and are sorted as follows:
818     *  1. sort on simple names of entities
819     *  2. if equal, then compare the DocKind ex: Package, Interface etc.
820     *  3a. if equal and if the type is of ExecutableMemberDoc(Constructor, Methods),
821     *      a case insensitive comparison of parameter the type signatures
822     *  3b. if equal, case sensitive comparison of the type signatures
823     *  4. finally, if equal, compare the FQNs of the entities
824     * @return a comparator for index file use
825     */
826    public Comparator<Doc> makeComparatorForIndexUse() {
827        return new Utils.DocComparator<Doc>() {
828            /**
829             * Compare two given Doc entities, first sort on names, then on the kinds,
830             * then on the parameters only if the type is an instance of ExecutableMemberDocs,
831             * the parameters are compared and finally the fully qualified names.
832             *
833             * @param d1 - a Doc element.
834             * @param d2 - a Doc element.
835             * @return a negative integer, zero, or a positive integer as the first
836             *         argument is less than, equal to, or greater than the second.
837             */
838            public int compare(Doc d1, Doc d2) {
839                int result = compareNames(d1, d2);
840                if (result != 0) {
841                    return result;
842                }
843                result = compareDocKinds(d1, d2);
844                if (result != 0) {
845                    return result;
846                }
847                if (hasParameters(d1)) {
848                    Parameter[] param1 = ((ExecutableMemberDoc) d1).parameters();
849                    Parameter[] param2 = ((ExecutableMemberDoc) d2).parameters();
850                    result = compareParameters(false, param1, param2);
851                    if (result != 0) {
852                        return result;
853                    }
854                    result = compareParameters(true, param1, param2);
855                    if (result != 0) {
856                        return result;
857                    }
858                }
859                return compareFullyQualifiedNames(d1, d2);
860            }
861        };
862    }
863    /**
864     * Comparator for ClassUse presentations, and sorted as follows,
865     * 1. compares simple names of entities
866     * 2. if equal, the fully qualified names of the entities
867     * 3. if equal and if applicable, the string representation of parameter types
868     * 3a. first by using case insensitive comparison
869     * 3b. second by using a case sensitive comparison
870     * 4. finally the Doc kinds ie. package, class, interface etc.
871     * @return a comparator to sort classes and members for class use
872     */
873    public Comparator<Doc> makeComparatorForClassUse() {
874        return new Utils.DocComparator<Doc>() {
875            /**
876             * Compares two given Doc entities, first sort on name, and if
877             * applicable on the fully qualified name, and if applicable
878             * on the parameter types, and finally the DocKind.
879             * @param d1 - a Doc element.
880             * @param d2 - a Doc element.
881             * @return a negative integer, zero, or a positive integer as the first
882             *         argument is less than, equal to, or greater than the second.
883             */
884            public int compare(Doc d1, Doc d2) {
885                int result = compareNames(d1, d2);
886                if (result != 0) {
887                    return result;
888                }
889                result = compareFullyQualifiedNames(d1, d2);
890                if (result != 0) {
891                    return result;
892                }
893                if (hasParameters(d1) && hasParameters(d2)) {
894                    Parameter[] param1 = ((ExecutableMemberDoc) d1).parameters();
895                    Parameter[] param2 = ((ExecutableMemberDoc) d2).parameters();
896                    result = compareParameters(false, param1, param2);
897                    if (result != 0) {
898                        return result;
899                    }
900                    return compareParameters(true, param1, param2);
901                }
902                return compareDocKinds(d1, d2);
903            }
904        };
905    }
906    /**
907     * A general purpose comparator to sort Doc entities, basically provides the building blocks
908     * for creating specific comparators for an use-case.
909     * @param <T> a Doc entity
910     */
911    static abstract class DocComparator<T extends Doc> implements Comparator<Doc> {
912        static enum DocKind {
913           PACKAGE,
914           CLASS,
915           ENUM,
916           INTERFACE,
917           ANNOTATION,
918           FIELD,
919           CONSTRUCTOR,
920           METHOD
921        };
922        boolean hasParameters(Doc d) {
923            return d instanceof ExecutableMemberDoc;
924        }
925        DocKind getDocKind(Doc d) {
926            if (d.isAnnotationType() || d.isAnnotationTypeElement()) {
927                return DocKind.ANNOTATION;
928            } else if (d.isEnum() || d.isEnumConstant()) {
929                return DocKind.ENUM;
930            } else if (d.isField()) {
931                return DocKind.FIELD;
932            } else if (d.isInterface()) {
933                return DocKind.INTERFACE;
934            } else if (d.isClass()) {
935                return DocKind.CLASS;
936            } else if (d.isConstructor()) {
937                return DocKind.CONSTRUCTOR;
938            } else if (d.isMethod()) {
939                return DocKind.METHOD;
940            } else {
941                return DocKind.PACKAGE;
942            }
943        }
944        /**
945         * Compares two Doc entities' kinds, and these are ordered as defined in
946         * the DocKind enumeration.
947         * @param d1 the first Doc object
948         * @param d2 the second Doc object
949         * @return a negative integer, zero, or a positive integer as the first
950         *         argument is less than, equal to, or greater than the second.
951         */
952        protected int compareDocKinds(Doc d1, Doc d2) {
953            return getDocKind(d1).compareTo(getDocKind(d2));
954        }
955        /**
956         * Compares arrays of parameters as a string representation of their types.
957         *
958         * @param ignoreCase specifies case sensitive or insensitive comparison.
959         * @param params1 the first parameter array.
960         * @param params2 the first parameter array.
961         * @return a negative integer, zero, or a positive integer as the first argument is less
962         * than, equal to, or greater than the second.
963         */
964        protected int compareParameters(boolean caseSensitive,
965                                        Parameter[] params1,
966                                        Parameter[] params2) {
967            String s1 = getParametersAsString(params1);
968            String s2 = getParametersAsString(params2);
969            return compareStrings(caseSensitive, s1, s2);
970        }
971        /*
972         * This method returns a string representation solely for comparison purposes.
973         */
974        protected String getParametersAsString(Parameter[] params) {
975            StringBuilder sb = new StringBuilder();
976            for (Parameter param : params) {
977                Type t = param.type();
978                // add parameter type to arrays, as TypeMirror does.
979                String tname = (t.asParameterizedType() != null && t.getElementType() != null)
980                        ? t.getElementType() + t.dimension()
981                        : t.toString();
982                // prefix P for primitive and R for reference types, thus items will
983                // be ordered naturally.
984                sb.append(t.isPrimitive() ? "P" : "R").append("-").append(tname).append("-");
985            }
986            return sb.toString();
987        }
988
989        /**
990         * Compares two Doc entities typically the simple name of a method,
991         * field, constructor etc.
992         * @param d1 the first Doc.
993         * @param d2 the second Doc.
994         * @return a negative integer, zero, or a positive integer as the first
995         *         argument is less than, equal to, or greater than the second.
996         */
997        protected int compareNames(Doc d1, Doc d2) {
998            return compareStrings(d1.name(), d2.name());
999        }
1000
1001        /**
1002         * Compares the fully qualified names of the entities
1003         * @param d1 the first entity
1004         * @param d2 the second entity
1005         * @return a negative integer, zero, or a positive integer as the first
1006         *         argument is less than, equal to, or greater than the second.
1007         */
1008        protected int compareFullyQualifiedNames(Doc d1, Doc d2) {
1009            String name1 = (d1 instanceof ProgramElementDoc)
1010                    ? ((ProgramElementDoc)d1).qualifiedName()
1011                    : d1.name();
1012            String name2 = (d2 instanceof ProgramElementDoc)
1013                    ? ((ProgramElementDoc)d2).qualifiedName()
1014                    : d2.name();
1015            return compareStrings(name1, name2);
1016        }
1017    }
1018}
1019