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.ByteArrayOutputStream;
  import java.io.File;
  import java.io.OutputStream;
  import java.nio.charset.StandardCharsets;
  
  import org.junit.jupiter.api.Test;
  
  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
  import com.puppycrawl.tools.checkstyle.api.DetailAST;
  import com.puppycrawl.tools.checkstyle.api.FileContents;
  import com.puppycrawl.tools.checkstyle.api.FileText;
  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
  import com.puppycrawl.tools.checkstyle.api.Violation;
  import com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck;
  import com.puppycrawl.tools.checkstyle.checks.coding.NestedForDepthCheck;
  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck;
  import com.puppycrawl.tools.checkstyle.checks.whitespace.MethodParamPadCheck;
  import com.puppycrawl.tools.checkstyle.internal.utils.CloseAndFlushTestByteArrayOutputStream;
  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
  
  public class XpathFileGeneratorAuditListenerTest {
  
      /** OS specific line separator. */
      private static final String EOL = System.getProperty("line.separator");
  
      private static final Violation FIRST_MESSAGE = createViolation(3, 51,
              TokenTypes.LCURLY, null, LeftCurlyCheck.class);
  
      private static final Violation SECOND_MESSAGE = createViolation(15, 5,
              TokenTypes.METHOD_DEF, "MyModule", MethodParamPadCheck.class);
  
      private static final Violation THIRD_MESSAGE = createViolation(17, 13,
              TokenTypes.LITERAL_FOR, null, NestedForDepthCheck.class);
  
      private static final Violation FOURTH_MESSAGE = createViolation(5, 5,
              TokenTypes.VARIABLE_DEF, "JavadocModuleId", JavadocVariableCheck.class);
  
      private final CloseAndFlushTestByteArrayOutputStream outStream =
              new CloseAndFlushTestByteArrayOutputStream();
  
      static {
          try {
              constructEvents();
          }
          catch (Exception ex) {
              throw new ExceptionInInitializerError(ex);
          }
      }
  
      private static void constructEvents() throws Exception {
          final TreeWalkerAuditEvent event1 = createTreeWalkerAuditEvent(
                  "InputXpathFileGeneratorAuditListener.java", FIRST_MESSAGE);
  
          final TreeWalkerAuditEvent event2 = createTreeWalkerAuditEvent(
                  "InputXpathFileGeneratorAuditListener.java", SECOND_MESSAGE);
  
          final TreeWalkerAuditEvent event3 = createTreeWalkerAuditEvent(
                  "InputXpathFileGeneratorAuditListener.java", THIRD_MESSAGE);
  
          final TreeWalkerAuditEvent event4 = createTreeWalkerAuditEvent(
                  "InputXpathFileGeneratorAuditListener.java", FOURTH_MESSAGE);
  
          final XpathFileGeneratorAstFilter astFilter = new XpathFileGeneratorAstFilter();
          astFilter.accept(event1);
          astFilter.accept(event2);
          astFilter.accept(event3);
          astFilter.accept(event4);
      }
  
      @Test
      public void testFinishLocalSetup() {
          final OutputStream out = new ByteArrayOutputStream();
          final XpathFileGeneratorAuditListener listener =
                  new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
  
          listener.finishLocalSetup();
          listener.auditStarted(null);
          listener.auditFinished(null);
          final String actual = out.toString();
          assertWithMessage("Output should be empty")
              .that(actual)
              .isEmpty();
      }
  
      @Test
      public void testFileStarted() {
          final OutputStream out = new ByteArrayOutputStream();
          final XpathFileGeneratorAuditListener listener =
                  new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
          final AuditEvent ev = new AuditEvent(this, "Test.java", null);
          listener.fileStarted(ev);
          listener.auditFinished(null);
          final String actual = out.toString();
          assertWithMessage("Output should be empty")
              .that(actual)
              .isEmpty();
      }
  
      @Test
      public void testFileFinished() {
          final OutputStream out = new ByteArrayOutputStream();
          final XpathFileGeneratorAuditListener listener =
                  new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
          final AuditEvent ev = new AuditEvent(this, "Test.java", null);
          listener.fileFinished(ev);
          listener.auditFinished(null);
          final String actual = out.toString();
          assertWithMessage("Output should be empty")
              .that(actual)
              .isEmpty();
      }
  
      @Test
      public void testAddException() {
          final OutputStream out = new ByteArrayOutputStream();
          final XpathFileGeneratorAuditListener logger =
                  new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
          logger.auditStarted(null);
          final Violation violation =
                  new Violation(1, 1,
                          "messages.properties", null, null, null, getClass(), null);
          final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
  
          try {
              logger.addException(ev, null);
              assertWithMessage("Exception is excepted").fail();
          }
          catch (UnsupportedOperationException ex) {
              assertWithMessage("Invalid exception message")
                  .that(ex.getMessage())
                  .isEqualTo("Operation is not supported");
          }
      }
  
      @Test
      public void testCorrectOne() {
          final AuditEvent event = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
                  FIRST_MESSAGE);
  
          final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
                  + "<!DOCTYPE suppressions PUBLIC" + EOL
                  + "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
                  + "//EN\"" + EOL
                  + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">"
                  + EOL
                  + "<suppressions>" + EOL
                  + "<suppress-xpath" + EOL
                  + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
                  + "       checks=\"LeftCurlyCheck\""
                  + EOL
                  + "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
                  + "[@text='InputXpathFileGeneratorAuditListener']]"
                  + "/OBJBLOCK/LCURLY\"/>" + EOL
                  + "</suppressions>" + EOL;
  
          verifyOutput(expected, event);
      }
  
      @Test
      public void testCorrectTwo() {
          final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
                  SECOND_MESSAGE);
  
          final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
                  THIRD_MESSAGE);
  
          final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
                  + "<!DOCTYPE suppressions PUBLIC" + EOL
                  + "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
                  + "//EN\"" + EOL
                  + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">"
                  + EOL
                  + "<suppressions>" + EOL
                  + "<suppress-xpath" + EOL
                  + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
                  + "       id=\"MyModule\"" + EOL
                  + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
                  + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
                  + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]\"/>" + EOL
                  + "<suppress-xpath" + EOL
                  + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
                  + "       checks=\"NestedForDepthCheck\"" + EOL
                  + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
                  + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
                  + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]/SLIST/LITERAL_FOR/SLIST"
                  + "/LITERAL_FOR\"/>" + EOL
                  + "</suppressions>" + EOL;
  
          verifyOutput(expected, event1, event2);
      }
  
      @Test
      public void testOnlyOneMatching() {
          final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
                  10, 5, MethodParamPadCheck.class);
  
          final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
                  5, 5, JavadocVariableCheck.class);
  
          final AuditEvent event3 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
                  FOURTH_MESSAGE);
  
          final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
                  + "<!DOCTYPE suppressions PUBLIC" + EOL
                  + "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
                  + "//EN\"" + EOL
                  + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">"
                  + EOL
                  + "<suppressions>" + EOL
                  + "<suppress-xpath" + EOL
                  + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
                  + "       id=\"JavadocModuleId\"" + EOL
                  + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
                  + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
                  + "/OBJBLOCK/VARIABLE_DEF[./IDENT[@text='isValid']]\"/>" + EOL
                  + "</suppressions>" + EOL;
  
          verifyOutput(expected, event1, event2, event3);
      }
  
      @Test
      public void testCloseStream() {
          final XpathFileGeneratorAuditListener listener =
                  new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.CLOSE);
          listener.finishLocalSetup();
          listener.auditStarted(null);
          listener.auditFinished(null);
  
          assertWithMessage("Invalid close count")
              .that(outStream.getCloseCount())
              .isEqualTo(1);
      }
  
      @Test
      public void testNoCloseStream() {
          final XpathFileGeneratorAuditListener listener =
                  new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.NONE);
          listener.finishLocalSetup();
          listener.auditStarted(null);
          listener.auditFinished(null);
  
          assertWithMessage("Invalid close count")
              .that(outStream.getCloseCount())
              .isEqualTo(0);
      }
  
      private AuditEvent createAuditEvent(String fileName, int lineNumber, int columnNumber,
                                          Class<?> sourceClass) {
          final Violation violation =
                  new Violation(lineNumber, columnNumber, "messages.properties", null,
                          null, null, sourceClass, null);
  
          return new AuditEvent(this,
                  getPath(fileName), violation);
      }
  
      private AuditEvent createAuditEvent(String fileName, Violation violation) {
          return new AuditEvent(this,
                  getPath(fileName), violation);
      }
  
      private static Violation createViolation(int lineNumber,
                                                                     int columnNumber, int tokenType,
                                                                     String moduleId,
                                                                     Class<?> sourceClass) {
          return new Violation(lineNumber, columnNumber, tokenType,
                  "messages.properties", null, null,
                  SeverityLevel.ERROR, moduleId, sourceClass, null);
      }
  
      private static TreeWalkerAuditEvent createTreeWalkerAuditEvent(String fileName,
                                                                     Violation violation)
              throws Exception {
          final File file = new File(getPath(fileName));
          final FileText fileText = new FileText(
                  file.getAbsoluteFile(),
                  System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
          final FileContents fileContents = new FileContents(fileText);
          final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
  
          return new TreeWalkerAuditEvent(fileContents, fileName,
                  violation, rootAst);
      }
  
      private static String getPath(String filename) {
          return "src/test/resources/com/puppycrawl/tools/checkstyle/xpathfilegeneratorauditlistener/"
                  + filename;
      }
  
      private static void verifyOutput(String expected, AuditEvent... events) {
          final TestByteArrayOutputStream out = new TestByteArrayOutputStream();
  
          final XpathFileGeneratorAuditListener listener =
                  new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
  
          for (AuditEvent event : events) {
              listener.addError(event);
          }
  
          listener.auditFinished(null);
  
          assertWithMessage("Output stream flush count")
                  .that(out.flushCount)
                  .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(1));
          assertWithMessage("Output stream close count")
                  .that(out.closeCount)
                  .isEqualTo(1);
  
          final String actual = out.toString();
          assertWithMessage("Invalid suppressions file content")
              .that(actual)
              .isEqualTo(expected);
      }
  
      private static final class TestByteArrayOutputStream extends ByteArrayOutputStream {
  
          private int closeCount;
          private int flushCount;
  
          @Override
          public void close() {
              closeCount++;
          }
  
          @Override
          public void flush() {
              flushCount++;
          }
  
      }
  }
  