1 /*
2  * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.javadoc.main;
27 
28 import java.io.File;
29 import java.io.IOException;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.EnumSet;
33 import java.util.HashSet;
34 import java.util.LinkedHashMap;
35 import java.util.LinkedHashSet;
36 import java.util.Map;
37 import java.util.Set;
38 
39 import javax.tools.JavaFileManager;
40 import javax.tools.JavaFileManager.Location;
41 import javax.tools.JavaFileObject;
42 import javax.tools.StandardJavaFileManager;
43 import javax.tools.StandardLocation;
44 
45 import com.sun.tools.javac.code.ClassFinder;
46 import com.sun.tools.javac.code.Symbol.Completer;
47 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
48 import com.sun.tools.javac.code.Symbol.PackageSymbol;
49 import com.sun.tools.javac.comp.Enter;
50 import com.sun.tools.javac.tree.JCTree;
51 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
52 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
53 import com.sun.tools.javac.util.Abort;
54 import com.sun.tools.javac.util.Context;
55 import com.sun.tools.javac.util.List;
56 import com.sun.tools.javac.util.ListBuffer;
57 import com.sun.tools.javac.util.Name;
58 
59 
60 /**
61  *  This class could be the main entry point for Javadoc when Javadoc is used as a
62  *  component in a larger software system. It provides operations to
63  *  construct a new javadoc processor, and to run it on a set of source
64  *  files.
65  *
66  *  <p><b>This is NOT part of any supported API.
67  *  If you write code that depends on this, you do so at your own risk.
68  *  This code and its internal interfaces are subject to change or
69  *  deletion without notice.</b>
70  *
71  *  @author Neal Gafter
72  */
73 @Deprecated(since="9", forRemoval=true)
74 @SuppressWarnings("removal")
75 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
76     DocEnv docenv;
77 
78     final Messager messager;
79     final ClassFinder javadocFinder;
80     final Enter javadocEnter;
81     final Set<JavaFileObject> uniquefiles;
82 
83     /**
84      * Construct a new JavaCompiler processor, using appropriately
85      * extended phases of the underlying compiler.
86      */
JavadocTool(Context context)87     protected JavadocTool(Context context) {
88         super(context);
89         messager = Messager.instance0(context);
90         javadocFinder = JavadocClassFinder.instance(context);
91         javadocEnter = JavadocEnter.instance(context);
92         uniquefiles = new HashSet<>();
93     }
94 
95     /**
96      * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
97      */
98     @Override
keepComments()99     protected boolean keepComments() {
100         return true;
101     }
102 
103     /**
104      *  Construct a new javadoc tool.
105      */
make0(Context context)106     public static JavadocTool make0(Context context) {
107         // force the use of Javadoc's class finder
108         JavadocClassFinder.preRegister(context);
109 
110         // force the use of Javadoc's own enter phase
111         JavadocEnter.preRegister(context);
112 
113         // force the use of Javadoc's own member enter phase
114         JavadocMemberEnter.preRegister(context);
115 
116         // force the use of Javadoc's own todo phase
117         JavadocTodo.preRegister(context);
118 
119         // force the use of Messager as a Log
120         Messager.instance0(context);
121 
122         return new JavadocTool(context);
123     }
124 
getRootDocImpl(String doclocale, String encoding, ModifierFilter filter, List<String> args, List<String[]> options, Iterable<? extends JavaFileObject> fileObjects, boolean breakiterator, List<String> subPackages, List<String> excludedPackages, boolean docClasses, boolean legacyDoclet, boolean quiet)125     public RootDocImpl getRootDocImpl(String doclocale,
126                                       String encoding,
127                                       ModifierFilter filter,
128                                       List<String> args,
129                                       List<String[]> options,
130                                       Iterable<? extends JavaFileObject> fileObjects,
131                                       boolean breakiterator,
132                                       List<String> subPackages,
133                                       List<String> excludedPackages,
134                                       boolean docClasses,
135                                       boolean legacyDoclet,
136                       boolean quiet) throws IOException {
137         docenv = DocEnv.instance(context);
138         docenv.showAccess = filter;
139         docenv.quiet = quiet;
140         docenv.breakiterator = breakiterator;
141         docenv.setLocale(doclocale);
142         docenv.setEncoding(encoding);
143         docenv.docClasses = docClasses;
144         docenv.legacyDoclet = legacyDoclet;
145 
146         javadocFinder.sourceCompleter = docClasses ? Completer.NULL_COMPLETER : sourceCompleter;
147 
148         if (docClasses) {
149             // If -Xclasses is set, the args should be a series of class names
150             for (String arg: args) {
151                 if (!isValidPackageName(arg)) // checks
152                     docenv.error(null, "main.illegal_class_name", arg);
153             }
154             if (messager.nerrors() != 0) {
155                 return null;
156             }
157             return new RootDocImpl(docenv, args, options);
158         }
159 
160         ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<>();
161         Set<String> includedPackages = new LinkedHashSet<>();
162 
163         try {
164             StandardJavaFileManager fm = docenv.fileManager instanceof StandardJavaFileManager
165                     ? (StandardJavaFileManager) docenv.fileManager : null;
166             Set<String> packageNames = new LinkedHashSet<>();
167             // Normally, the args should be a series of package names or file names.
168             // Parse the files and collect the package names.
169             for (String arg: args) {
170                 if (fm != null && arg.endsWith(".java") && new File(arg).exists()) {
171                     if (new File(arg).getName().equals("module-info.java")) {
172                         docenv.warning(null, "main.file_ignored", arg);
173                     } else {
174                         parse(fm.getJavaFileObjects(arg), classTrees, true);
175                     }
176                 } else if (isValidPackageName(arg)) {
177                     packageNames.add(arg);
178                 } else if (arg.endsWith(".java")) {
179                     if (fm == null)
180                         throw new IllegalArgumentException();
181                     else
182                         docenv.error(null, "main.file_not_found", arg);
183                 } else {
184                     docenv.error(null, "main.illegal_package_name", arg);
185                 }
186             }
187 
188             // Parse file objects provide via the DocumentationTool API
189             parse(fileObjects, classTrees, true);
190 
191             modules.initModules(classTrees.toList());
192 
193             // Build up the complete list of any packages to be documented
194             Location location = modules.multiModuleMode ? StandardLocation.MODULE_SOURCE_PATH
195                     : docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH) ? StandardLocation.SOURCE_PATH
196                     : StandardLocation.CLASS_PATH;
197 
198             PackageTable t = new PackageTable(docenv.fileManager, location)
199                     .packages(packageNames)
200                     .subpackages(subPackages, excludedPackages);
201 
202             includedPackages = t.getIncludedPackages();
203 
204             // Parse the files in the packages to be documented
205             ListBuffer<JCCompilationUnit> packageTrees = new ListBuffer<>();
206             for (String packageName: includedPackages) {
207                 List<JavaFileObject> files = t.getFiles(packageName);
208                 docenv.notice("main.Loading_source_files_for_package", packageName);
209 
210                 if (files.isEmpty())
211                     messager.warning(Messager.NOPOS, "main.no_source_files_for_package", packageName);
212                 parse(files, packageTrees, false);
213             }
214             modules.enter(packageTrees.toList(), null);
215 
216             if (messager.nerrors() != 0) {
217                 return null;
218             }
219 
220             // Enter symbols for all files
221             docenv.notice("main.Building_tree");
222             javadocEnter.main(classTrees.toList().appendList(packageTrees.toList()));
223         } catch (Abort ex) {}
224 
225         if (messager.nerrors() != 0)
226             return null;
227 
228         return new RootDocImpl(docenv, listClasses(classTrees.toList()), List.from(includedPackages), options);
229     }
230 
231     /** Is the given string a valid package name? */
isValidPackageName(String s)232     boolean isValidPackageName(String s) {
233         int index;
234         while ((index = s.indexOf('.')) != -1) {
235             if (!isValidClassName(s.substring(0, index))) return false;
236             s = s.substring(index+1);
237         }
238         return isValidClassName(s);
239     }
240 
parse(Iterable<? extends JavaFileObject> files, ListBuffer<JCCompilationUnit> trees, boolean trace)241     private void parse(Iterable<? extends JavaFileObject> files, ListBuffer<JCCompilationUnit> trees,
242                        boolean trace) {
243         for (JavaFileObject fo: files) {
244             if (uniquefiles.add(fo)) { // ignore duplicates
245                 if (trace)
246                     docenv.notice("main.Loading_source_file", fo.getName());
247                 trees.append(parse(fo));
248             }
249         }
250     }
251 
252     /** Are surrogates supported?
253      */
254     final static boolean surrogatesSupported = surrogatesSupported();
surrogatesSupported()255     private static boolean surrogatesSupported() {
256         try {
257             boolean b = Character.isHighSurrogate('a');
258             return true;
259         } catch (NoSuchMethodError ex) {
260             return false;
261         }
262     }
263 
264     /**
265      * Return true if given file name is a valid class name
266      * (including "package-info").
267      * @param s the name of the class to check.
268      * @return true if given class name is a valid class name
269      * and false otherwise.
270      */
isValidClassName(String s)271     public static boolean isValidClassName(String s) {
272         if (s.length() < 1) return false;
273         if (s.equals("package-info")) return true;
274         if (surrogatesSupported) {
275             int cp = s.codePointAt(0);
276             if (!Character.isJavaIdentifierStart(cp))
277                 return false;
278             for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
279                 cp = s.codePointAt(j);
280                 if (!Character.isJavaIdentifierPart(cp))
281                     return false;
282             }
283         } else {
284             if (!Character.isJavaIdentifierStart(s.charAt(0)))
285                 return false;
286             for (int j=1; j<s.length(); j++)
287                 if (!Character.isJavaIdentifierPart(s.charAt(j)))
288                     return false;
289         }
290         return true;
291     }
292 
293     /**
294      * From a list of top level trees, return the list of contained class definitions
295      */
listClasses(List<JCCompilationUnit> trees)296     List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
297         ListBuffer<JCClassDecl> result = new ListBuffer<>();
298         for (JCCompilationUnit t : trees) {
299             for (JCTree def : t.defs) {
300                 if (def.hasTag(JCTree.Tag.CLASSDEF))
301                     result.append((JCClassDecl)def);
302             }
303         }
304         return result.toList();
305     }
306 
307     /**
308      * A table to manage included and excluded packages.
309      */
310     class PackageTable {
311         private final Map<String, Entry> entries = new LinkedHashMap<>();
312         private final Set<String> includedPackages = new LinkedHashSet<>();
313         private final JavaFileManager fm;
314         private final Location location;
315         private final Set<JavaFileObject.Kind> sourceKinds = EnumSet.of(JavaFileObject.Kind.SOURCE);
316 
317         /**
318          * Creates a table to manage included and excluded packages.
319          * @param fm The file manager used to locate source files
320          * @param locn the location used to locate source files
321          */
PackageTable(JavaFileManager fm, Location locn)322         PackageTable(JavaFileManager fm, Location locn) {
323             this.fm = fm;
324             this.location = locn;
325             getEntry("").excluded = false;
326         }
327 
packages(Collection<String> packageNames)328         PackageTable packages(Collection<String> packageNames) {
329             includedPackages.addAll(packageNames);
330             return this;
331         }
332 
subpackages(Collection<String> packageNames, Collection<String> excludePackageNames)333         PackageTable subpackages(Collection<String> packageNames, Collection<String> excludePackageNames)
334                 throws IOException {
335             for (String p: excludePackageNames) {
336                 getEntry(p).excluded = true;
337             }
338 
339             for (String packageName: packageNames) {
340                 Location packageLocn = getLocation(packageName);
341                 for (JavaFileObject fo: fm.list(packageLocn, packageName, sourceKinds, true)) {
342                     String binaryName = fm.inferBinaryName(packageLocn, fo);
343                     String pn = getPackageName(binaryName);
344                     String simpleName = getSimpleName(binaryName);
345                     Entry e = getEntry(pn);
346                     if (!e.isExcluded() && isValidClassName(simpleName)) {
347                         includedPackages.add(pn);
348                         e.files = (e.files == null ? List.of(fo) : e.files.prepend(fo));
349                     }
350                 }
351             }
352             return this;
353         }
354 
355         /**
356          * Returns the aggregate set of included packages.
357          * @return the aggregate set of included packages
358          */
getIncludedPackages()359         Set<String> getIncludedPackages() {
360             return includedPackages;
361         }
362 
363         /**
364          * Returns the set of source files for a package.
365          * @param packageName the specified package
366          * @return the set of file objects for the specified package
367          * @throws IOException if an error occurs while accessing the files
368          */
getFiles(String packageName)369         List<JavaFileObject> getFiles(String packageName) throws IOException {
370             Entry e = getEntry(packageName);
371             // The files may have been found as a side effect of searching for subpackages
372             if (e.files != null)
373                 return e.files;
374 
375             ListBuffer<JavaFileObject> lb = new ListBuffer<>();
376             Location packageLocn = getLocation(packageName);
377             for (JavaFileObject fo: fm.list(packageLocn, packageName, sourceKinds, false)) {
378                 String binaryName = fm.inferBinaryName(packageLocn, fo);
379                 String simpleName = getSimpleName(binaryName);
380                 if (isValidClassName(simpleName)) {
381                     lb.append(fo);
382                 }
383             }
384 
385             return lb.toList();
386         }
387 
getLocation(String packageName)388         private Location getLocation(String packageName) throws IOException {
389             if (location == StandardLocation.MODULE_SOURCE_PATH) {
390                 // TODO: handle invalid results
391                 Name pack = names.fromString(packageName);
392 
393                 for (ModuleSymbol msym : modules.allModules()) {
394                     PackageSymbol p = syms.getPackage(msym, pack);
395                     if (p != null && !p.members().isEmpty()) {
396                         return fm.getLocationForModule(location, msym.name.toString());
397                     }
398                 }
399 
400                 return null;
401             } else {
402                 return location;
403             }
404         }
405 
getEntry(String name)406         private Entry getEntry(String name) {
407             Entry e = entries.get(name);
408             if (e == null)
409                 entries.put(name, e = new Entry(name));
410             return e;
411         }
412 
getPackageName(String name)413         private String getPackageName(String name) {
414             int lastDot = name.lastIndexOf(".");
415             return (lastDot == -1 ? "" : name.substring(0, lastDot));
416         }
417 
getSimpleName(String name)418         private String getSimpleName(String name) {
419             int lastDot = name.lastIndexOf(".");
420             return (lastDot == -1 ? name : name.substring(lastDot + 1));
421         }
422 
423         class Entry {
424             final String name;
425             Boolean excluded;
426             List<JavaFileObject> files;
427 
Entry(String name)428             Entry(String name) {
429                 this.name = name;
430             }
431 
isExcluded()432             boolean isExcluded() {
433                 if (excluded == null)
434                     excluded = getEntry(getPackageName(name)).isExcluded();
435                 return excluded;
436             }
437         }
438     }
439 
440 }
441