001/*
002 * Copyright (c) 2012, 2022, Oracle and/or its affiliates. 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.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclint;
027
028import java.io.File;
029import java.io.IOException;
030import java.io.PrintWriter;
031import java.util.ArrayList;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Queue;
035
036import javax.lang.model.element.Name;
037import javax.lang.model.util.Elements;
038import javax.lang.model.util.Types;
039import javax.tools.StandardLocation;
040
041import com.sun.source.doctree.DocCommentTree;
042import com.sun.source.tree.BlockTree;
043import com.sun.source.tree.ClassTree;
044import com.sun.source.tree.CompilationUnitTree;
045import com.sun.source.tree.LambdaExpressionTree;
046import com.sun.source.tree.ModuleTree;
047import com.sun.source.tree.PackageTree;
048import com.sun.source.tree.MethodTree;
049import com.sun.source.tree.Tree;
050import com.sun.source.tree.VariableTree;
051import com.sun.source.util.DocTrees;
052import com.sun.source.util.JavacTask;
053import com.sun.source.util.TaskEvent;
054import com.sun.source.util.TaskListener;
055import com.sun.source.util.TreePath;
056import com.sun.source.util.TreePathScanner;
057import com.sun.tools.javac.api.JavacTaskImpl;
058import com.sun.tools.javac.api.JavacTool;
059import com.sun.tools.javac.file.JavacFileManager;
060import com.sun.tools.javac.main.JavaCompiler;
061import com.sun.tools.javac.util.Context;
062import com.sun.tools.javac.util.DefinedBy;
063import com.sun.tools.javac.util.DefinedBy.Api;
064
065/**
066 * Multi-function entry point for the doc check utility.
067 *
068 * This class can be invoked in the following ways:
069 * <ul>
070 * <li>From the command line
071 * <li>From javac, as a plugin
072 * <li>Directly, via a simple API
073 * </ul>
074 */
075public class DocLint extends com.sun.tools.doclint.DocLint {
076
077    public static final String XMSGS_OPTION = "-Xmsgs";
078    public static final String XMSGS_CUSTOM_PREFIX = "-Xmsgs:";
079    private static final String STATS = "-stats";
080    public static final String XCUSTOM_TAGS_PREFIX = "-XcustomTags:";
081    public static final String XCHECK_PACKAGE = "-XcheckPackage:";
082    public static final String SEPARATOR = ",";
083
084    // <editor-fold defaultstate="collapsed" desc="Command-line entry point">
085    public static void main(String... args) {
086        DocLint dl = new DocLint();
087        try {
088            dl.run(args);
089        } catch (BadArgs e) {
090            System.err.println(e.getMessage());
091            System.exit(1);
092        } catch (IOException e) {
093            System.err.println(
094                dl.localize("dc.main.ioerror", e.getLocalizedMessage()));
095            System.exit(2);
096        }
097    }
098
099    // </editor-fold>
100
101    // <editor-fold defaultstate="collapsed" desc="Simple API">
102
103    public class BadArgs extends Exception {
104        private static final long serialVersionUID = 0;
105
106        BadArgs(String code, Object... args) {
107            super(localize(code, args));
108            this.code = code;
109            this.args = args;
110        }
111
112        final String code;
113        final transient Object[] args;
114    }
115
116    /**
117     * Simple API entry point.
118     * @param args Options and operands for doclint
119     * @throws BadArgs if an error is detected in any args
120     * @throws IOException if there are problems with any of the file arguments
121     */
122    public void run(String... args) throws BadArgs, IOException {
123        PrintWriter out = new PrintWriter(System.out);
124        try {
125            run(out, args);
126        } finally {
127            out.flush();
128        }
129    }
130
131    public void run(PrintWriter out, String... args)
132            throws BadArgs, IOException {
133        env = new Env();
134        processArgs(args);
135
136        boolean noFiles = javacFiles.isEmpty();
137        if (needHelp) {
138            showHelp(out);
139            if (noFiles)
140                return;
141        } else if (noFiles) {
142            out.println(localize("dc.main.no.files.given"));
143            return;
144        }
145
146        JavacTool tool = JavacTool.create();
147
148        JavacFileManager fm = new JavacFileManager(new Context(), false, null);
149        fm.setSymbolFileEnabled(false);
150        if (javacBootClassPath != null) {
151            fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH,
152                javacBootClassPath);
153        }
154        if (javacClassPath != null) {
155            fm.setLocation(StandardLocation.CLASS_PATH, javacClassPath);
156        }
157        if (javacSourcePath != null) {
158            fm.setLocation(StandardLocation.SOURCE_PATH, javacSourcePath);
159        }
160
161        JavacTask task = tool.getTask(out, fm, null, javacOpts, null,
162            fm.getJavaFileObjectsFromFiles(javacFiles));
163        Iterable<? extends CompilationUnitTree> units = task.parse();
164        ((JavacTaskImpl) task).enter();
165
166        env.init(task);
167        checker = new Checker(env);
168
169        DeclScanner ds = new DeclScanner(env) {
170            @Override
171            void visitDecl(Tree tree, Name name) {
172                TreePath p = getCurrentPath();
173                DocCommentTree dc = env.trees.getDocCommentTree(p);
174
175                checker.scan(dc, p);
176            }
177        };
178
179        ds.scan(units, null);
180
181        reportStats(out);
182
183        Context ctx = ((JavacTaskImpl) task).getContext();
184        JavaCompiler c = JavaCompiler.instance(ctx);
185        c.printCount("error", c.errorCount());
186        c.printCount("warn", c.warningCount());
187    }
188
189    void processArgs(String... args) throws BadArgs {
190        javacOpts = new ArrayList<>();
191        javacFiles = new ArrayList<>();
192
193        if (args.length == 0)
194            needHelp = true;
195
196        for (int i = 0; i < args.length; i++) {
197            String arg = args[i];
198            if (arg.matches("-Xmax(errs|warns)") && i + 1 < args.length) {
199                if (args[++i].matches("[0-9]+")) {
200                    javacOpts.add(arg);
201                    javacOpts.add(args[i]);
202                } else {
203                    throw new BadArgs("dc.bad.value.for.option", arg, args[i]);
204                }
205            } else if ((arg.equals("-target") || arg.equals("-source"))
206                && i + 1 < args.length) {
207                javacOpts.add(arg);
208                javacOpts.add(args[++i]);
209            } else if (arg.equals(STATS)) {
210                env.messages.setStatsEnabled(true);
211            } else if (arg.equals("-bootclasspath") && i + 1 < args.length) {
212                javacBootClassPath = splitPath(args[++i]);
213            } else if (arg.equals("-classpath") && i + 1 < args.length) {
214                javacClassPath = splitPath(args[++i]);
215            } else if (arg.equals("-cp") && i + 1 < args.length) {
216                javacClassPath = splitPath(args[++i]);
217            } else if (arg.equals("-sourcepath") && i + 1 < args.length) {
218                javacSourcePath = splitPath(args[++i]);
219            } else if (arg.equals(XMSGS_OPTION)) {
220                env.messages.setOptions(null);
221            } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) {
222                env.messages.setOptions(arg.substring(arg.indexOf(":") + 1));
223            } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) {
224                env.setCustomTags(arg.substring(arg.indexOf(":") + 1));
225            } else if (arg.equals("-h") || arg.equals("-help")
226                || arg.equals("--help")
227                || arg.equals("-?") || arg.equals("-usage")) {
228                needHelp = true;
229            } else if (arg.startsWith("-")) {
230                throw new BadArgs("dc.bad.option", arg);
231            } else {
232                while (i < args.length)
233                    javacFiles.add(new File(args[i++]));
234            }
235        }
236    }
237
238    void showHelp(PrintWriter out) {
239        String msg = localize("dc.main.usage");
240        for (String line : msg.split("\n"))
241            out.println(line);
242    }
243
244    List<File> splitPath(String path) {
245        List<File> files = new ArrayList<>();
246        for (String f : path.split(File.pathSeparator)) {
247            if (f.length() > 0)
248                files.add(new File(f));
249        }
250        return files;
251    }
252
253    List<File> javacBootClassPath;
254    List<File> javacClassPath;
255    List<File> javacSourcePath;
256    List<String> javacOpts;
257    List<File> javacFiles;
258    boolean needHelp = false;
259
260    // </editor-fold>
261
262    // <editor-fold defaultstate="collapsed" desc="javac Plugin">
263
264    @Override
265    @DefinedBy(Api.COMPILER_TREE)
266    public String getName() {
267        return "doclint";
268    }
269
270    @Override
271    @DefinedBy(Api.COMPILER_TREE)
272    public void init(JavacTask task, String... args) {
273        init(task, args, true);
274    }
275
276    // </editor-fold>
277
278    // <editor-fold defaultstate="collapsed" desc="Embedding API">
279
280    /**
281     * Initialize DocLint for use with a {@code JavacTask}.
282     * {@link Env#strictReferenceChecks Strict reference checks} are <em>not</em> enabled by default.
283     *
284     * @param task            the task
285     * @param args            arguments to configure DocLint
286     * @param addTaskListener whether or not to register a {@code TaskListener} to invoke DocLint
287     */
288    public void init(JavacTask task, String[] args, boolean addTaskListener) {
289        env = new Env();
290        env.init(task);
291        processArgs(env, args);
292
293        checker = new Checker(env);
294
295        if (addTaskListener) {
296            final DeclScanner ds = new DeclScanner(env) {
297                @Override
298                void visitDecl(Tree tree, Name name) {
299                    TreePath p = getCurrentPath();
300                    DocCommentTree dc = env.trees.getDocCommentTree(p);
301                    checker.scan(dc, p);
302                }
303            };
304
305            TaskListener tl = new TaskListener() {
306                @Override
307                @DefinedBy(Api.COMPILER_TREE)
308                public void started(TaskEvent e) {
309                    switch (e.getKind()) {
310                    case ANALYZE:
311                        CompilationUnitTree tree;
312                        while ((tree = todo.poll()) != null)
313                            ds.scan(tree, null);
314                        break;
315                    }
316                }
317
318                @Override
319                @DefinedBy(Api.COMPILER_TREE)
320                public void finished(TaskEvent e) {
321                    switch (e.getKind()) {
322                    case PARSE:
323                        todo.add(e.getCompilationUnit());
324                        break;
325                    }
326                }
327
328                Queue<CompilationUnitTree> todo = new LinkedList<>();
329            };
330
331            task.addTaskListener(tl);
332        }
333    }
334
335    /**
336     * Initialize DocLint with the given utility objects and arguments.
337     * {@link Env#strictReferenceChecks Strict reference checks} <em>are</em> enabled by default.
338     *
339     * @param trees    the {@code DocTrees} utility class
340     * @param elements the {@code Elements} utility class
341     * @param types    the {@code Types} utility class
342     * @param args     arguments to configure DocLint
343     */
344    public void init(DocTrees trees, Elements elements, Types types,
345            String... args) {
346        env = new Env();
347        env.init(trees, elements, types);
348        env.strictReferenceChecks = true;
349        processArgs(env, args);
350
351        checker = new Checker(env);
352    }
353
354    private void processArgs(Env env, String... args) {
355        for (String arg : args) {
356            if (arg.equals(XMSGS_OPTION)) {
357                env.messages.setOptions(null);
358            } else if (arg.startsWith(XMSGS_CUSTOM_PREFIX)) {
359                env.messages.setOptions(arg.substring(arg.indexOf(":") + 1));
360            } else if (arg.startsWith(XCUSTOM_TAGS_PREFIX)) {
361                env.setCustomTags(arg.substring(arg.indexOf(":") + 1));
362            } else if (arg.startsWith(XCHECK_PACKAGE)) {
363                env.setCheckPackages(arg.substring(arg.indexOf(":") + 1));
364            } else
365                throw new IllegalArgumentException(arg);
366        }
367    }
368
369    public void scan(TreePath p) {
370        DocCommentTree dc = env.trees.getDocCommentTree(p);
371        checker.scan(dc, p);
372    }
373
374    public boolean shouldCheck(CompilationUnitTree unit) {
375        return env.shouldCheck(unit);
376    }
377
378    public void reportStats(PrintWriter out) {
379        env.messages.reportStats(out);
380    }
381
382    // </editor-fold>
383
384    Env env;
385    Checker checker;
386
387    public boolean isValidOption(String opt) {
388        if (opt.equals(XMSGS_OPTION))
389            return true;
390        if (opt.startsWith(XMSGS_CUSTOM_PREFIX))
391            return Messages.Options
392                .isValidOptions(opt.substring(XMSGS_CUSTOM_PREFIX.length()));
393        if (opt.startsWith(XCHECK_PACKAGE)) {
394            return Env.validatePackages(opt.substring(opt.indexOf(":") + 1));
395        }
396        return false;
397    }
398
399    public boolean isGroupEnabled(Messages.Group group,
400            Env.AccessKind accessKind) {
401        return env.messages.isEnabled(group, accessKind);
402    }
403
404    private String localize(String code, Object... args) {
405        Messages m = (env != null) ? env.messages : new Messages(null);
406        return m.localize(code, args);
407    }
408
409    // <editor-fold defaultstate="collapsed" desc="DeclScanner">
410
411    abstract static class DeclScanner extends TreePathScanner<Void, Void> {
412        final Env env;
413
414        public DeclScanner(Env env) {
415            this.env = env;
416        }
417
418        abstract void visitDecl(Tree tree, Name name);
419
420        @Override
421        @DefinedBy(Api.COMPILER_TREE)
422        public Void visitPackage(PackageTree tree, Void ignore) {
423            visitDecl(tree, null);
424            return super.visitPackage(tree, ignore);
425        }
426
427        @Override
428        @DefinedBy(Api.COMPILER_TREE)
429        public Void visitClass(ClassTree tree, Void ignore) {
430            visitDecl(tree, tree.getSimpleName());
431            return super.visitClass(tree, ignore);
432        }
433
434        @Override
435        @DefinedBy(Api.COMPILER_TREE)
436        public Void visitMethod(MethodTree tree, Void ignore) {
437            visitDecl(tree, tree.getName());
438            return null;
439        }
440
441        @Override
442        @DefinedBy(Api.COMPILER_TREE)
443        public Void visitModule(ModuleTree tree, Void ignore) {
444            visitDecl(tree, null);
445            return super.visitModule(tree, ignore);
446        }
447
448        @Override
449        @DefinedBy(Api.COMPILER_TREE)
450        public Void visitVariable(VariableTree tree, Void ignore) {
451            visitDecl(tree, tree.getName());
452            return super.visitVariable(tree, ignore);
453        }
454
455        @Override
456        @DefinedBy(Api.COMPILER_TREE)
457        public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
458            if (!env.shouldCheck(node)) {
459                return null;
460            }
461            return super.visitCompilationUnit(node, p);
462        }
463
464        @Override
465        @DefinedBy(Api.COMPILER_TREE)
466        public Void visitBlock(BlockTree tree, Void ignore) {
467            return null;
468        }
469
470        @Override
471        @DefinedBy(Api.COMPILER_TREE)
472        public Void visitLambdaExpression(LambdaExpressionTree tree,
473                Void ignore) {
474            return null;
475        }
476
477    }
478
479    // </editor-fold>
480
481}