code: ///////////////////////////////////////////////////////////////////////////////////////////////
  // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
  // Copyright (C) 2001-2023 the original author or authors.
  //
  // This library is free software; you can redistribute it and/or
  // modify it under the terms of the GNU Lesser General Public
  // License as published by the Free Software Foundation; either
  // version 2.1 of the License, or (at your option) any later version.
  //
  // This library is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  // Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public
  // License along with this library; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  ///////////////////////////////////////////////////////////////////////////////////////////////
  
  package com.puppycrawl.tools.checkstyle;
  
  import static com.google.common.truth.Truth.assertWithMessage;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.nio.charset.StandardCharsets;
  import java.util.Locale;
  
  import org.apache.commons.io.FileUtils;
  import org.itsallcode.io.Capturable;
  import org.itsallcode.junit.sysextensions.SystemErrGuard;
  import org.itsallcode.junit.sysextensions.SystemErrGuard.SysErr;
  import org.itsallcode.junit.sysextensions.SystemOutGuard;
  import org.itsallcode.junit.sysextensions.SystemOutGuard.SysOut;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.extension.ExtendWith;
  
  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
  
  @ExtendWith({SystemErrGuard.class, SystemOutGuard.class})
  public class JavadocPropertiesGeneratorTest extends AbstractPathTestSupport {
  
      private static final String EOL = System.lineSeparator();
      private static final String USAGE = String.format(Locale.ROOT,
              "Usage: java com.puppycrawl.tools.checkstyle.JavadocPropertiesGenerator [-hV]%n"
                      + "       --destfile=<outputFile> <inputFile>%n"
                      + "      <inputFile>   The input file.%n"
                      + "      --destfile=<outputFile>%n"
                      + "                    The output file.%n"
                      + "  -h, --help        Show this help message and exit.%n"
                      + "  -V, --version     Print version information and exit.%n");
      private static final File DESTFILE = new File("target/tokentypes.properties");
      private static final String DESTFILE_ABSOLUTE_PATH = DESTFILE.getAbsolutePath();
  
      @Override
      protected String getPackageLocation() {
          return "com/puppycrawl/tools/checkstyle/javadocpropertiesgenerator";
      }
  
      /**
       * Returns canonical path for the file with the given file name.
       * The path is formed base on the non-compilable resources location.
       * This implementation uses 'src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/'
       * as a non-compilable resource location.
       *
       * @param filename file name.
       * @return canonical path for the file with the given file name.
       * @throws IOException if I/O exception occurs while forming the path.
       */
      protected final String getNonCompilablePath(String filename) throws IOException {
          return new File("src/test/resources-noncompilable/" + getPackageLocation() + "/"
                  + filename).getCanonicalPath();
      }
  
      /**
       * <p>Configures the environment for each test.</p>
       * <ul>
       * <li>Start output capture for {@link System#err} and {@link System#out}</li>
       * <li>Turn off colors for picocli to not conflict with tests if they are auto turned on.</li>
       * </ul>
       *
       * @param systemErr wrapper for {@code System.err}
       * @param systemOut wrapper for {@code System.out}
       */
      @BeforeEach
      public void setUp(@SysErr Capturable systemErr, @SysOut Capturable systemOut) {
          systemErr.captureMuted();
          systemOut.captureMuted();
  
          System.setProperty("picocli.ansi", "false");
      }
  
      @Test
      public void testIsProperUtilsClass() throws ReflectiveOperationException {
          assertWithMessage("Constructor is not private")
              .that(TestUtil.isUtilsClassHasPrivateConstructor(
                                              JavadocPropertiesGenerator.class))
              .isTrue();
      }
  
      @Test
      public void testNonExistentArgument(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
              throws Exception {
          JavadocPropertiesGenerator.main("--nonexistent-argument");
  
          final String expected = String.format(Locale.ROOT,
                  "Missing required options and parameters: "
                  + "'--destfile=<outputFile>', '<inputFile>'%n")
                  + USAGE;
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo(expected);
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
      }
  
      @Test
      public void testNoDestfileSpecified(@SysErr Capturable systemErr,
              @SysOut Capturable systemOut) throws Exception {
          JavadocPropertiesGenerator.main(getPath("InputMain.java"));
  
          final String expected = String.format(Locale.ROOT,
                  "Missing required option: '--destfile=<outputFile>'%n") + USAGE;
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo(expected);
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
      }
  
      @Test
      public void testNoInputSpecified(@SysErr Capturable systemErr,
              @SysOut Capturable systemOut) throws Exception {
          JavadocPropertiesGenerator.main("--destfile", DESTFILE_ABSOLUTE_PATH);
  
          final String expected = String.format(Locale.ROOT,
                  "Missing required parameter: '<inputFile>'%n") + USAGE;
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo(expected);
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
      }
  
      @Test
      public void testNotClass(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
              throws Exception {
          JavadocPropertiesGenerator.main("--destfile", DESTFILE_ABSOLUTE_PATH,
              getPath("InputJavadocPropertiesGeneratorNotClass.java"));
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
      }
  
      @Test
      public void testNotExistentInputSpecified(@SysErr Capturable systemErr,
              @SysOut Capturable systemOut) {
          try {
              JavadocPropertiesGenerator.main(
                  "--destfile", DESTFILE_ABSOLUTE_PATH, "NotExistent.java");
              assertWithMessage("Exception was expected").fail();
          }
          catch (CheckstyleException ex) {
              assertWithMessage("Invalid error message")
                  .that(ex.getMessage())
                  .isEqualTo("Failed to write javadoc properties of 'NotExistent.java' to '"
                      + DESTFILE_ABSOLUTE_PATH + "'");
  
              final Throwable cause = ex.getCause();
              assertWithMessage("Invalid error message")
                      .that(cause)
                      .isInstanceOf(FileNotFoundException.class);
              assertWithMessage("Invalid error message")
                      .that(cause)
                      .hasMessageThat()
                      .contains("NotExistent.java");
          }
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
      }
  
      @Test
      public void testInvalidDestinationSpecified(@SysErr Capturable systemErr,
              @SysOut Capturable systemOut) throws Exception {
          try {
              // Passing a folder name will cause the FileNotFoundException.
              JavadocPropertiesGenerator.main("--destfile", "..",
                  getPath("InputJavadocPropertiesGeneratorCorrect.java"));
              assertWithMessage("Exception was expected").fail();
          }
          catch (CheckstyleException ex) {
              final String expectedError = "Failed to write javadoc properties of '"
                  + getPath("InputJavadocPropertiesGeneratorCorrect.java") + "' to '..'";
              assertWithMessage("Invalid error message")
                  .that(ex.getMessage())
                  .isEqualTo(expectedError);
  
              final Throwable cause = ex.getCause();
              assertWithMessage("Invalid error message")
                      .that(cause)
                      .isInstanceOf(FileNotFoundException.class);
              assertWithMessage("Invalid error message")
                      .that(cause)
                      .hasMessageThat()
                      .contains("..");
          }
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
      }
  
      @Test
      public void testCorrect(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
              throws Exception {
          final String expectedContent = "EOF1=The end of file token." + EOL
              + "EOF2=The end of file token." + EOL
              + "TYPE_EXTENSION_AND='&amp;' symbol when used in a generic upper or lower bounds"
              + " constrain e.g&#46;"
              + " <code>Comparable<T extends Serializable & CharSequence></code>!" + EOL
              + "LCURLY=A left curly brace (<code>{</code>)." + EOL
              + "DEPRECATED_LITERAL='@deprecated' literal in @deprecated Javadoc tag?" + EOL;
  
          JavadocPropertiesGenerator.main(getPath("InputJavadocPropertiesGeneratorCorrect.java"),
              "--destfile", DESTFILE_ABSOLUTE_PATH);
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
          final String fileContent = FileUtils.readFileToString(DESTFILE, StandardCharsets.UTF_8);
          assertWithMessage("File content is not expected")
              .that(fileContent)
              .isEqualTo(expectedContent);
      }
  
      @Test
      public void testEmptyJavadoc(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
              throws Exception {
          JavadocPropertiesGenerator.main(getPath("InputJavadocPropertiesGeneratorEmptyJavadoc.java"),
              "--destfile", DESTFILE_ABSOLUTE_PATH);
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
          final long size = FileUtils.sizeOf(DESTFILE);
          assertWithMessage("File '" + DESTFILE + "' must be empty")
              .that(size)
              .isEqualTo(0);
      }
  
      @Test
      public void testNotConstants(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
              throws Exception {
          JavadocPropertiesGenerator.main(getPath("InputJavadocPropertiesGeneratorNotConstants.java"),
              "--destfile", DESTFILE_ABSOLUTE_PATH);
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo("");
          final long size = FileUtils.sizeOf(DESTFILE);
          assertWithMessage("File '" + DESTFILE + "' must be empty")
              .that(size)
              .isEqualTo(0);
      }
  
      @Test
      public void testHelp(@SysErr Capturable systemErr, @SysOut Capturable systemOut)
              throws Exception {
          JavadocPropertiesGenerator.main("-h");
          assertWithMessage("Unexpected error log")
              .that(systemErr.getCapturedData())
              .isEqualTo("");
          assertWithMessage("Unexpected output log")
              .that(systemOut.getCapturedData())
              .isEqualTo(USAGE);
      }
  
      @Test
      public void testJavadocParseError() throws Exception {
          final String path = getPath("InputJavadocPropertiesGeneratorJavadocParseError.java");
          try {
              JavadocPropertiesGenerator.main(path, "--destfile", DESTFILE_ABSOLUTE_PATH);
              assertWithMessage("Exception was expected").fail();
          }
          catch (IllegalArgumentException ex) {
              assertWithMessage("Invalid error message")
                      .that(ex.getMessage())
                      .contains("mismatched input '<EOF>' expecting JAVADOC_INLINE_TAG_END");
          }
          final long size = FileUtils.sizeOf(DESTFILE);
          assertWithMessage("File '" + DESTFILE + "' must be empty")
              .that(size)
              .isEqualTo(0);
      }
  
      @Test
      public void testNotImplementedTag() throws Exception {
          final String path = getPath("InputJavadocPropertiesGeneratorNotImplementedTag.java");
          try {
              JavadocPropertiesGenerator.main(path, "--destfile", DESTFILE_ABSOLUTE_PATH);
              assertWithMessage("Exception was expected").fail();
          }
          catch (CheckstyleException ex) {
              assertWithMessage("Invalid error message")
                  .that(ex.getMessage())
                  .isEqualTo("Unsupported inline tag LINK_LITERAL");
          }
          final long size = FileUtils.sizeOf(DESTFILE);
          assertWithMessage("File '" + DESTFILE + "' must be empty")
              .that(size)
              .isEqualTo(0);
      }
  
      @Test
      public void testParseError() throws Exception {
          final String path = getNonCompilablePath("InputJavadocPropertiesGeneratorParseError.java");
          try {
              JavadocPropertiesGenerator.main(path, "--destfile", DESTFILE_ABSOLUTE_PATH);
              assertWithMessage("Exception was expected").fail();
          }
          catch (CheckstyleException ex) {
              assertWithMessage("Invalid error message")
                      .that(ex.getMessage())
                      .contains("InputJavadocPropertiesGeneratorParseError.java");
  
              final Throwable cause = ex.getCause();
              assertWithMessage("Invalid error message")
                      .that(cause)
                      .isInstanceOf(IllegalStateException.class);
              assertWithMessage("Invalid error message")
                      .that(cause)
                      .hasMessageThat()
                      .contains("9:0: mismatched input '!' expecting '}'");
          }
      }
  
  }
  