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 
  24 import java.io.IOException;
  25 import java.io.PrintWriter;
  26 import java.io.StringWriter;
  27 import java.nio.file.Path;
  28 import java.nio.file.Paths;
  29 import java.util.Arrays;
  30 import java.util.HashSet;
  31 import java.util.List;
  32 import java.util.Locale;
  33 import java.util.Set;
  34 import java.util.TreeSet;
  35 import java.util.stream.Collectors;
  36 
  37 import javax.lang.model.SourceVersion;
  38 import javax.lang.model.element.Element;
  39 import javax.lang.model.element.ElementKind;
  40 import javax.lang.model.element.ModuleElement;
  41 import javax.lang.model.element.PackageElement;
  42 import javax.lang.model.element.TypeElement;
  43 import javax.lang.model.util.ElementFilter;
  44 import javax.lang.model.util.SimpleElementVisitor9;
  45 
  46 import jdk.javadoc.doclet.Doclet;
  47 import jdk.javadoc.doclet.DocletEnvironment;
  48 import jdk.javadoc.doclet.Reporter;
  49 
  50 import toolbox.JavadocTask;
  51 import toolbox.Task;
  52 import toolbox.Task.Expect;
  53 import toolbox.TestRunner;
  54 import toolbox.ToolBox;
  55 
  56 import static toolbox.Task.OutputKind.*;
  57 
  58 /**
  59  * Base class for module tests.
  60  */
  61 public 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 }