1/*
2 * Copyright (c) 2016, 2017, 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
24import java.io.IOException;
25import java.io.PrintWriter;
26import java.io.StringWriter;
27import java.nio.file.Path;
28import java.nio.file.Paths;
29import java.util.Arrays;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Locale;
33import java.util.Set;
34import java.util.TreeSet;
35import java.util.stream.Collectors;
36
37import javax.lang.model.SourceVersion;
38import javax.lang.model.element.Element;
39import javax.lang.model.element.ElementKind;
40import javax.lang.model.element.ModuleElement;
41import javax.lang.model.element.PackageElement;
42import javax.lang.model.element.TypeElement;
43import javax.lang.model.util.ElementFilter;
44import javax.lang.model.util.SimpleElementVisitor9;
45
46import jdk.javadoc.doclet.Doclet;
47import jdk.javadoc.doclet.DocletEnvironment;
48import jdk.javadoc.doclet.Reporter;
49
50import toolbox.JavadocTask;
51import toolbox.Task;
52import toolbox.Task.Expect;
53import toolbox.TestRunner;
54import toolbox.ToolBox;
55
56import static toolbox.Task.OutputKind.*;
57
58/**
59 * Base class for module tests.
60 */
61public class ModuleTestBase extends TestRunner {
62
63    // Field Separator
64    private static final String FS = " ";
65
66    protected ToolBox tb;
67    private final Class<?> docletClass;
68
69    private Task.Result currentTask = null;
70
71    ModuleTestBase() {
72        super(System.err);
73        tb = new ToolBox();
74        ClassLoader cl = ModuleTestBase.class.getClassLoader();
75        try {
76            docletClass = cl.loadClass("ModuleTestBase$ModulesTesterDoclet");
77        } catch (ClassNotFoundException cfe) {
78            throw new Error(cfe);
79        }
80    }
81
82    /**
83     * Execute methods annotated with @Test, and throw an exception if any
84     * errors are reported..
85     *
86     * @throws Exception if any errors occurred
87     */
88    protected void runTests() throws Exception {
89        runTests(m -> new Object[] { Paths.get(m.getName()) });
90    }
91
92    Task.Result execTask(String... args) {
93        return execTask0(false, args);
94    }
95
96    Task.Result execNegativeTask(String... args) {
97        return execTask0(true, args);
98    }
99
100    private Task.Result execTask0(boolean isNegative, String... args) {
101        JavadocTask et = new JavadocTask(tb, Task.Mode.API);
102        et.docletClass(docletClass);
103        //Arrays.asList(args).forEach((a -> System.err.println("arg: " + a)));
104        System.err.println(Arrays.asList(args));
105        currentTask = isNegative
106                ? et.options(args).run(Expect.FAIL)
107                : et.options(args).run();
108        return currentTask;
109    }
110
111    Path[] findHtmlFiles(Path... paths) throws IOException {
112        return tb.findFiles(".html", paths);
113    }
114
115    boolean grep(String regex, Path file) throws Exception {
116        List<String> lines = tb.readAllLines(file);
117        List<String> foundList = tb.grep(regex, lines);
118        return !foundList.isEmpty();
119    }
120
121    String normalize(String in) {
122        return in.replace('\\', '/');
123    }
124
125    void checkModulesSpecified(String... args) throws Exception {
126        for (String arg : args) {
127            checkDocletOutputPresent("Specified", ElementKind.MODULE, arg);
128        }
129    }
130
131    void checkPackagesSpecified(String... args) throws Exception {
132        for (String arg : args) {
133            checkDocletOutputPresent("Specified", ElementKind.PACKAGE, arg);
134        }
135    }
136
137    void checkTypesSpecified(String... args) throws Exception {
138        for (String arg : args) {
139            checkDocletOutputPresent("Specified", ElementKind.CLASS, arg);
140        }
141    }
142
143    void checkModulesIncluded(String... args) throws Exception {
144        for (String arg : args) {
145            checkDocletOutputPresent("Included", ElementKind.MODULE, arg);
146        }
147    }
148
149    void checkPackagesIncluded(String... args) throws Exception {
150        for (String arg : args) {
151            checkDocletOutputPresent("Included", ElementKind.PACKAGE, arg);
152        }
153    }
154
155    void checkTypesIncluded(String... args) throws Exception {
156        for (String arg : args) {
157            checkDocletOutputPresent("Included", ElementKind.CLASS, arg);
158        }
159    }
160
161    void checkTypesSelected(String... args) throws Exception {
162        for (String arg : args) {
163            checkDocletOutputPresent("Selected", ElementKind.CLASS, arg);
164        }
165    }
166
167    void checkMembersSelected(String... args) throws Exception {
168        for (String arg : args) {
169            checkDocletOutputPresent("Selected", ElementKind.METHOD, arg);
170        }
171    }
172
173    void checkModuleMode(String mode) throws Exception {
174        assertPresent("^ModuleMode" + FS + mode);
175    }
176
177    void checkStringPresent(String regex) throws Exception {
178        assertPresent(regex);
179    }
180
181    void checkDocletOutputPresent(String category, ElementKind kind, String regex) throws Exception {
182        assertPresent("^" + category + " " + kind.toString() + " " + regex);
183    }
184
185    void assertPresent(String regex) throws Exception {
186        assertPresent(regex, STDOUT);
187    }
188
189    void assertMessagePresent(String regex) throws Exception {
190        assertPresent(regex, Task.OutputKind.DIRECT);
191    }
192
193    void assertMessageNotPresent(String regex) throws Exception {
194        assertNotPresent(regex, Task.OutputKind.DIRECT);
195    }
196
197    void assertPresent(String regex, Task.OutputKind kind) throws Exception {
198        List<String> foundList = tb.grep(regex, currentTask.getOutputLines(kind));
199        if (foundList.isEmpty()) {
200            dumpDocletDiagnostics();
201            throw new Exception(regex + " not found in: " + kind);
202        }
203    }
204
205    void assertNotPresent(String regex, Task.OutputKind kind) throws Exception {
206        List<String> foundList = tb.grep(regex, currentTask.getOutputLines(kind));
207        if (!foundList.isEmpty()) {
208            dumpDocletDiagnostics();
209            throw new Exception(regex + " found in: " + kind);
210        }
211    }
212
213    void dumpDocletDiagnostics() {
214        for (Task.OutputKind kind : Task.OutputKind.values()) {
215            String output = currentTask.getOutput(kind);
216            if (output != null && !output.isEmpty()) {
217                System.err.println("<" + kind + ">");
218                System.err.println(output);
219            }
220        }
221    }
222
223    void checkModulesNotSpecified(String... args) throws Exception {
224        for (String arg : args) {
225            checkDocletOutputAbsent("Specified", ElementKind.MODULE, arg);
226        }
227    }
228
229    void checkPackagesNotSpecified(String... args) throws Exception {
230        for (String arg : args) {
231            checkDocletOutputAbsent("Specified", ElementKind.PACKAGE, arg);
232        }
233    }
234
235    void checkTypesNotSpecified(String... args) throws Exception {
236        for (String arg : args) {
237            checkDocletOutputAbsent("Specified", ElementKind.CLASS, arg);
238        }
239    }
240
241    void checkModulesNotIncluded(String... args) throws Exception {
242        for (String arg : args) {
243            checkDocletOutputAbsent("Included", ElementKind.MODULE, arg);
244        }
245    }
246
247    void checkPackagesNotIncluded(String... args) throws Exception {
248        for (String arg : args) {
249            checkDocletOutputAbsent("Included", ElementKind.PACKAGE, arg);
250        }
251    }
252
253    void checkTypesNotIncluded(String... args) throws Exception {
254        for (String arg : args) {
255            checkDocletOutputAbsent("Included", ElementKind.CLASS, arg);
256        }
257    }
258
259    void checkMembersNotSelected(String... args) throws Exception {
260        for (String arg : args) {
261            checkDocletOutputAbsent("Selected", ElementKind.METHOD, arg);
262        }
263    }
264
265    void checkStringAbsent(String regex) throws Exception {
266        assertAbsent(regex);
267    }
268
269    void checkDocletOutputAbsent(String category, ElementKind kind, String regex) throws Exception {
270        assertAbsent("^" + category + FS + kind.toString() + FS + regex);
271    }
272
273    void assertAbsent(String regex) throws Exception {
274        assertAbsent(regex, STDOUT);
275    }
276
277    void assertAbsent(String regex, Task.OutputKind kind) throws Exception {
278        List<String> foundList = tb.grep(regex, currentTask.getOutputLines(kind));
279        if (!foundList.isEmpty()) {
280            dumpDocletDiagnostics();
281            throw new Exception(regex + " found in: " + kind);
282        }
283    }
284
285    public static class ModulesTesterDoclet implements Doclet {
286        StringWriter sw = new StringWriter();
287        PrintWriter ps = new PrintWriter(sw);
288
289        DocletEnvironment docEnv = null;
290
291        boolean hasDocComments = false;
292
293        String hasDocComments(Element e) {
294            String comment = docEnv.getElementUtils().getDocComment(e);
295            return comment != null && !comment.isEmpty()
296                    ? "hasDocComments"
297                    : "noDocComments";
298        }
299
300        // csv style output, for simple regex verification
301        void printDataSet(String header, Set<? extends Element> set) {
302            for (Element e : set) {
303                ps.print(header);
304                new SimpleElementVisitor9<Void, Void>() {
305                    @Override
306                    public Void visitModule(ModuleElement e, Void p) {
307                        ps.print(FS);
308                        ps.print(e.getKind());
309                        ps.print(FS);
310                        ps.print(e.getQualifiedName());
311                        if (hasDocComments) {
312                            ps.print(FS);
313                            ps.print(hasDocComments(e));
314                        }
315                        ps.println();
316                        return null;
317                    }
318
319                    @Override
320                    public Void visitPackage(PackageElement e, Void p) {
321                        ps.print(FS);
322                        ps.print(e.getKind());
323                        ps.print(FS);
324                        ps.print(e.getQualifiedName());
325                        if (hasDocComments) {
326                            ps.print(FS);
327                            ps.print(hasDocComments(e));
328                        }
329                        ps.println();
330                        return null;
331                    }
332
333                    @Override
334                    public Void visitType(TypeElement e, Void p) {
335                        ps.print(FS);
336                        ps.print(ElementKind.CLASS);
337                        ps.print(FS);
338                        ps.print(e.getQualifiedName());
339                        if (hasDocComments) {
340                            ps.print(FS);
341                            ps.print(hasDocComments(e));
342                        }
343                        ps.println();
344                        return null;
345                    }
346
347                    @Override
348                    protected Void defaultAction(Element e, Void p) {
349                        Element encl = e.getEnclosingElement();
350                        CharSequence fqn = new SimpleElementVisitor9<CharSequence, Void>() {
351                            @Override
352                            public CharSequence visitModule(ModuleElement e, Void p) {
353                                return e.getQualifiedName();
354                            }
355
356                            @Override
357                            public CharSequence visitType(TypeElement e, Void p) {
358                                return e.getQualifiedName();
359                            }
360
361                            @Override
362                            public CharSequence visitPackage(PackageElement e, Void p) {
363                                return e.getQualifiedName();
364                            }
365
366                        }.visit(encl);
367
368                        ps.print(FS);
369                        ps.print(ElementKind.METHOD); // always METHOD
370                        ps.print(FS);
371                        ps.print(fqn);
372                        ps.print(".");
373                        ps.print(e.getSimpleName());
374                        if (hasDocComments) {
375                            ps.print(FS);
376                            ps.print(hasDocComments(e));
377                        }
378                        ps.println();
379                        return null;
380                    }
381                }.visit(e);
382            }
383        }
384
385        @Override
386        public boolean run(DocletEnvironment docenv) {
387            this.docEnv = docenv;
388            ps.println("ModuleMode" + FS + docenv.getModuleMode());
389            printDataSet("Specified", docenv.getSpecifiedElements());
390            printDataSet("Included", docenv.getIncludedElements());
391            printDataSet("Selected", getAllSelectedElements(docenv));
392            System.out.println(sw);
393            return true;
394        }
395
396        Set<Element> getAllSelectedElements(DocletEnvironment docenv) {
397            Set<Element> result = new TreeSet<Element>((Element e1, Element e2) -> {
398                // some grouping by kind preferred
399                int rc = e1.getKind().compareTo(e2.getKind());
400                if (rc != 0) return rc;
401                rc = e1.toString().compareTo(e2.toString());
402                if (rc != 0) return rc;
403                return Integer.compare(e1.hashCode(), e2.hashCode());
404            });
405            Set<? extends Element> elements = docenv.getIncludedElements();
406            for (ModuleElement me : ElementFilter.modulesIn(elements)) {
407                addEnclosedElements(docenv, result, me);
408            }
409            for (PackageElement pe : ElementFilter.packagesIn(elements)) {
410                ModuleElement mdle = docenv.getElementUtils().getModuleOf(pe);
411                if (mdle != null)
412                    addEnclosedElements(docenv, result, mdle);
413                addEnclosedElements(docenv, result, pe);
414            }
415            for (TypeElement te : ElementFilter.typesIn(elements)) {
416                addEnclosedElements(docenv, result, te);
417            }
418            return result;
419        }
420
421        void addEnclosedElements(DocletEnvironment docenv, Set<Element> result, Element e) {
422            List<Element> elems = e.getEnclosedElements().stream()
423                    .filter(el -> docenv.isIncluded(el))
424                    .collect(Collectors.toList());
425            result.addAll(elems);
426            for (TypeElement t : ElementFilter.typesIn(elems)) {
427                addEnclosedElements(docenv, result, t);
428            }
429        }
430
431        @Override
432        public Set<Doclet.Option> getSupportedOptions() {
433            Option[] options = {
434                new Option() {
435                    private final List<String> someOption = Arrays.asList(
436                            "-hasDocComments"
437                    );
438
439                    @Override
440                    public int getArgumentCount() {
441                        return 0;
442                    }
443
444                    @Override
445                    public String getDescription() {
446                        return "print disposition of doc comments on an element";
447                    }
448
449                    @Override
450                    public Option.Kind getKind() {
451                        return Option.Kind.STANDARD;
452                    }
453
454                    @Override
455                    public List<String> getNames() {
456                        return someOption;
457                    }
458
459                    @Override
460                    public String getParameters() {
461                        return "flag";
462                    }
463
464                    @Override
465                    public boolean process(String opt, List<String> arguments) {
466                        hasDocComments = true;
467                        return true;
468                    }
469                }
470            };
471            return new HashSet<>(Arrays.asList(options));
472        }
473
474        @Override
475        public void init(Locale locale, Reporter reporter) {}
476
477        @Override
478        public String getName() {
479            return "ModulesTesterDoclet";
480        }
481
482        @Override
483        public SourceVersion getSupportedSourceVersion() {
484            return SourceVersion.latest();
485        }
486    }
487}
488