1/*
2 * Copyright (c) 2016, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package toolbox;
25
26import java.io.File;
27import java.io.IOException;
28import java.io.PrintWriter;
29import java.nio.file.Path;
30import java.nio.file.Paths;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
37import java.util.stream.Collectors;
38import java.util.stream.Stream;
39
40import javax.tools.DocumentationTool.DocumentationTask;
41import javax.tools.DocumentationTool;
42import javax.tools.JavaFileManager;
43import javax.tools.JavaFileManager.Location;
44import javax.tools.JavaFileObject;
45import javax.tools.StandardJavaFileManager;
46import javax.tools.StandardLocation;
47import javax.tools.ToolProvider;
48
49import jdk.javadoc.internal.api.JavadocTool;
50
51/**
52 * A task to configure and run the documentation tool, javadoc.
53 */
54public class JavadocTask extends AbstractTask<JavadocTask> {
55    private boolean includeStandardOptions;
56    private List<Path> classpath;
57    private List<Path> sourcepath;
58    private Path outdir;
59    private List<String> options;
60    private List<String> classes;
61    private List<String> files;
62    private List<JavaFileObject> fileObjects;
63    private JavaFileManager fileManager;
64
65    private JavadocTool jdtool;
66    private StandardJavaFileManager internalFileManager;
67    private Class<?> docletClass = null; // use the standard doclet by default
68
69    /**
70     * Creates a task to execute {@code javadoc} using API mode.
71     * @param toolBox the {@code ToolBox} to use
72     */
73    public JavadocTask(ToolBox toolBox) {
74        super(toolBox, Task.Mode.API);
75    }
76
77    /**
78     * Creates a task to execute {@code javadoc} in a specified mode.
79     * @param toolBox the {@code ToolBox} to use
80     * @param mode the mode to be used
81     */
82    public JavadocTask(ToolBox toolBox, Task.Mode mode) {
83        super(toolBox, mode);
84    }
85
86    /**
87     * Sets the classpath.
88     * @param classpath the classpath
89     * @return this task object
90     */
91    public JavadocTask classpath(String classpath) {
92        this.classpath = Stream.of(classpath.split(File.pathSeparator))
93                .filter(s -> !s.isEmpty())
94                .map(s -> Paths.get(s))
95                .collect(Collectors.toList());
96        return this;
97    }
98
99    /**
100     * Sets the classpath.
101     * @param classpath the classpath
102     * @return this task object
103     */
104    public JavadocTask classpath(Path... classpath) {
105        this.classpath = Arrays.asList(classpath);
106        return this;
107    }
108
109    /**
110     * Sets the classpath.
111     * @param classpath the classpath
112     * @return this task object
113     */
114    public JavadocTask classpath(List<Path> classpath) {
115        this.classpath = classpath;
116        return this;
117    }
118
119    /**
120     * Sets the sourcepath.
121     * @param sourcepath the sourcepath
122     * @return this task object
123     */
124    public JavadocTask sourcepath(String sourcepath) {
125        this.sourcepath = Stream.of(sourcepath.split(File.pathSeparator))
126                .filter(s -> !s.isEmpty())
127                .map(s -> Paths.get(s))
128                .collect(Collectors.toList());
129        return this;
130    }
131
132    /**
133     * Sets the sourcepath.
134     * @param sourcepath the sourcepath
135     * @return this task object
136     */
137    public JavadocTask sourcepath(Path... sourcepath) {
138        this.sourcepath = Arrays.asList(sourcepath);
139        return this;
140    }
141
142    /**
143     * Sets the sourcepath.
144     * @param sourcepath the sourcepath
145     * @return this task object
146     */
147    public JavadocTask sourcepath(List<Path> sourcepath) {
148        this.sourcepath = sourcepath;
149        return this;
150    }
151
152    /**
153     * Sets the output directory.
154     * @param outdir the output directory
155     * @return this task object
156     */
157    public JavadocTask outdir(String outdir) {
158        this.outdir = Paths.get(outdir);
159        return this;
160    }
161
162    /**
163     * Sets the output directory.
164     * @param outdir the output directory
165     * @return this task object
166     */
167    public JavadocTask outdir(Path outdir) {
168        this.outdir = outdir;
169        return this;
170    }
171
172    /**
173     * Sets the options.
174     * @param options the options
175     * @return this task object
176     */
177    public JavadocTask options(String... options) {
178        this.options = Arrays.asList(options);
179        return this;
180    }
181
182    /**
183     * Sets the options.
184     * @param options the options
185     * @return this task object
186     */
187    public JavadocTask options(List<String> options) {
188        this.options = options;
189        return this;
190    }
191
192    /**
193     * Sets the files to be documented.
194     * @param files the files
195     * @return this task object
196     */
197    public JavadocTask files(String... files) {
198        this.files = Arrays.asList(files);
199        return this;
200    }
201
202    /**
203     * Sets the files to be documented.
204     * @param files the files
205     * @return this task object
206     */
207    public JavadocTask files(Path... files) {
208        this.files = Stream.of(files)
209                .map(Path::toString)
210                .collect(Collectors.toList());
211        return this;
212    }
213
214    /**
215     * Sets the files to be documented.
216     * @param files the files
217     * @return this task object
218     */
219    public JavadocTask files(List<Path> files) {
220        this.files = files.stream()
221                .map(Path::toString)
222                .collect(Collectors.toList());
223        return this;
224    }
225
226    /**
227     * Sets the sources to be documented.
228     * Each source string is converted into an in-memory object that
229     * can be passed directly to the tool.
230     * @param sources the sources
231     * @return this task object
232     */
233    public JavadocTask sources(String... sources) {
234        fileObjects = Stream.of(sources)
235                .map(s -> new ToolBox.JavaSource(s))
236                .collect(Collectors.toList());
237        return this;
238    }
239
240    /**
241     * Sets the file manager to be used by this task.
242     * @param fileManager the file manager
243     * @return this task object
244     */
245    public JavadocTask fileManager(JavaFileManager fileManager) {
246        this.fileManager = fileManager;
247        return this;
248    }
249
250    /**
251     * Sets the doclet class to be invoked by javadoc.
252     * Note: this is applicable only in API mode.
253     * @param docletClass the user specified doclet
254     * @return this task object
255     */
256    public JavadocTask docletClass(Class<?> docletClass) {
257        this.docletClass = docletClass;
258        return this;
259    }
260
261    /**
262     * {@inheritDoc}
263     * @return the name "javadoc"
264     */
265    @Override
266    public String name() {
267        return "javadoc";
268    }
269
270    /**
271     * Calls the javadoc tool with the arguments as currently configured.
272     * @return a Result object indicating the outcome of the execution
273     * and the content of any output written to stdout, stderr, or the
274     * main stream by the tool.
275     */
276    @Override
277    public Task.Result run() {
278        if (mode == Task.Mode.EXEC)
279            return runExec();
280
281        AbstractTask.WriterOutput direct = new AbstractTask.WriterOutput();
282        // The following are to catch output to System.out and System.err,
283        // in case these are used instead of the primary (main) stream
284        AbstractTask.StreamOutput sysOut = new AbstractTask.StreamOutput(System.out, System::setOut);
285        AbstractTask.StreamOutput sysErr = new AbstractTask.StreamOutput(System.err, System::setErr);
286        int rc;
287        Map<Task.OutputKind, String> outputMap = new HashMap<>();
288        try {
289            switch (mode == null ? Task.Mode.API : mode) {
290                case API:
291                    rc = runAPI(direct.pw);
292                    break;
293                case CMDLINE:
294                    rc = runCommand(direct.pw);
295                    break;
296                default:
297                    throw new IllegalStateException();
298            }
299        } catch (IOException e) {
300            toolBox.out.println("Exception occurred: " + e);
301            rc = 99;
302        } finally {
303            outputMap.put(Task.OutputKind.STDOUT, sysOut.close());
304            outputMap.put(Task.OutputKind.STDERR, sysErr.close());
305            outputMap.put(Task.OutputKind.DIRECT, direct.close());
306        }
307        return checkExit(new Task.Result(toolBox, this, rc, outputMap));
308    }
309
310    private int runAPI(PrintWriter pw) throws IOException {
311        try {
312            jdtool = (JavadocTool) ToolProvider.getSystemDocumentationTool();
313            jdtool = new JavadocTool();
314
315            if (fileManager == null)
316                fileManager = internalFileManager = jdtool.getStandardFileManager(null, null, null);
317            if (outdir != null)
318                setLocationFromPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT,
319                        Collections.singletonList(outdir));
320            if (classpath != null)
321                setLocationFromPaths(StandardLocation.CLASS_PATH, classpath);
322            if (sourcepath != null)
323                setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcepath);
324            List<String> allOpts = new ArrayList<>();
325            if (options != null)
326                allOpts.addAll(options);
327
328            Iterable<? extends JavaFileObject> allFiles = joinFiles(files, fileObjects);
329            DocumentationTask task = jdtool.getTask(pw,
330                    fileManager,
331                    null,  // diagnostic listener; should optionally collect diags
332                    docletClass,
333                    allOpts,
334                    allFiles);
335            return ((DocumentationTask) task).call() ? 0 : 1;
336        } finally {
337            if (internalFileManager != null)
338                internalFileManager.close();
339        }
340    }
341
342    private void setLocationFromPaths(Location location, List<Path> files) throws IOException {
343        if (!(fileManager instanceof StandardJavaFileManager))
344            throw new IllegalStateException("not a StandardJavaFileManager");
345        ((StandardJavaFileManager) fileManager).setLocationFromPaths(location, files);
346    }
347
348    private int runCommand(PrintWriter pw) {
349        List<String> args = getAllArgs();
350        String[] argsArray = args.toArray(new String[args.size()]);
351        return jdk.javadoc.internal.tool.Main.execute(argsArray, pw);
352    }
353
354    private Task.Result runExec() {
355        List<String> args = new ArrayList<>();
356        Path javadoc = toolBox.getJDKTool("javadoc");
357        args.add(javadoc.toString());
358        if (includeStandardOptions) {
359            args.addAll(toolBox.split(System.getProperty("test.tool.vm.opts"), " +"));
360        }
361        args.addAll(getAllArgs());
362
363        String[] argsArray = args.toArray(new String[args.size()]);
364        ProcessBuilder pb = getProcessBuilder();
365        pb.command(argsArray);
366        try {
367            return runProcess(toolBox, this, pb.start());
368        } catch (IOException | InterruptedException e) {
369            throw new Error(e);
370        }
371    }
372
373    private List<String> getAllArgs() {
374        List<String> args = new ArrayList<>();
375        if (options != null)
376            args.addAll(options);
377        if (outdir != null) {
378            args.add("-d");
379            args.add(outdir.toString());
380        }
381        if (classpath != null) {
382            args.add("-classpath");
383            args.add(toSearchPath(classpath));
384        }
385        if (sourcepath != null) {
386            args.add("-sourcepath");
387            args.add(toSearchPath(sourcepath));
388        }
389        if (classes != null)
390            args.addAll(classes);
391        if (files != null)
392            args.addAll(files);
393
394        return args;
395    }
396
397    private String toSearchPath(List<Path> files) {
398        return files.stream()
399            .map(Path::toString)
400            .collect(Collectors.joining(File.pathSeparator));
401    }
402
403    private Iterable<? extends JavaFileObject> joinFiles(
404            List<String> files, List<JavaFileObject> fileObjects) {
405        if (files == null)
406            return fileObjects;
407        if (internalFileManager == null)
408            internalFileManager = jdtool.getStandardFileManager(null, null, null);
409        Iterable<? extends JavaFileObject> filesAsFileObjects =
410                internalFileManager.getJavaFileObjectsFromStrings(files);
411        if (fileObjects == null)
412            return filesAsFileObjects;
413        List<JavaFileObject> combinedList = new ArrayList<>();
414        for (JavaFileObject o : filesAsFileObjects)
415            combinedList.add(o);
416        combinedList.addAll(fileObjects);
417        return combinedList;
418    }
419}
420