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.assertThat;
  import static com.google.common.truth.Truth.assertWithMessage;
  
  import java.io.ByteArrayOutputStream;
  import java.nio.charset.StandardCharsets;
  import java.util.Set;
  import java.util.function.BiPredicate;
  
  import javax.xml.parsers.ParserConfigurationException;
  
  import org.w3c.dom.Document;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  
  import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
  
  public abstract class AbstractXmlTestSupport extends AbstractModuleTestSupport {
  
      /**
       * Returns the output stream xml.
       *
       * @param outputStream the byte array output stream.
       * @return returns a raw xml document of the interface {@link Document}.
       * @throws ParserConfigurationException can cause exception when parsing output stream.
       */
      protected static Document getOutputStreamXml(ByteArrayOutputStream outputStream)
              throws ParserConfigurationException {
          final String xml = outputStream.toString(StandardCharsets.UTF_8);
  
          return XmlUtil.getRawXml("audit output", xml, xml);
      }
  
      /**
       * Verifies the generated xml.
       *
       * @param expectedOutputFile the expected output file.
       * @param actualOutputStream the byte array output stream.
       * @param messages an array of expected messages/content of xml document.
       * @throws Exception can throw ComparisonFailure exception if xml encoding,
       *     version don't match.
       */
      protected static void verifyXml(String expectedOutputFile,
              ByteArrayOutputStream actualOutputStream, String... messages) throws Exception {
          verifyXml(expectedOutputFile, actualOutputStream, null, messages);
      }
  
      /**
       * Verifies the actual generated xml document by comparing with expected document.
       *
       * @param expectedOutputFile the expected output file.
       * @param actualOutputStream the actual byte array output stream.
       * @param ordered an ordered predicate for xml nodes.
       * @param messages an array of expected messages/content of xml document.
       * @throws Exception can throw ComparisonFailure exception if xml encoding,
       *     version do not match.
       */
      protected static void verifyXml(String expectedOutputFile,
              ByteArrayOutputStream actualOutputStream,
              BiPredicate<Node, Node> ordered, String... messages) throws Exception {
          String expectedContents = readFile(expectedOutputFile);
  
          for (int i = 0; i < messages.length; i++) {
              expectedContents = expectedContents.replace("$" + i, messages[i]);
          }
  
          final Document expectedDocument = XmlUtil.getRawXml("audit output", expectedContents,
                  expectedContents);
          final Document actualDocument = getOutputStreamXml(actualOutputStream);
  
          assertWithMessage("xml encoding should be the same")
                  .that(actualDocument.getXmlEncoding())
                  .isEqualTo(expectedDocument.getXmlEncoding());
  
          assertWithMessage("xml version should be the same")
                  .that(actualDocument.getXmlVersion())
                  .isEqualTo(expectedDocument.getXmlVersion());
  
          verifyXmlNode(expectedDocument, actualDocument, "/", ordered);
      }
  
      /**
       * Verifies if xml nodes in actual xml document and expected xml document match or not.
       *
       * @param expected the expected xml node. A {@link Node} interface.
       * @param actual the actual xml node. A {@link Node} interface.
       * @param path the path to the current xml node that are compared.
       * @param ordered an ordered predicate for xml nodes.
       */
      private static void verifyXmlNodes(Node expected, Node actual, String path,
              BiPredicate<Node, Node> ordered) {
          final Node expectedFirstChild = expected.getFirstChild();
          final Node actualFirstChild = actual.getFirstChild();
  
          if (expectedFirstChild == null) {
              assertWithMessage("no children nodes should exist: %s", path)
                      .that(actualFirstChild)
                      .isNull();
              assertWithMessage("text should be the same: %s", path)
                      .that(actual.getNodeValue())
                      .isEqualTo(expected.getNodeValue());
          }
          else {
              assertWithMessage("children nodes should exist: %s", path)
                      .that(actualFirstChild)
                      .isNotNull();
  
              if (ordered == null) {
                  Node actualChild = actualFirstChild;
  
                  for (Node expectedChild = expectedFirstChild; expectedChild != null;
                          expectedChild = expectedChild.getNextSibling()) {
                      verifyXmlNode(expectedChild, actualChild, path, ordered);
  
                      actualChild = actualChild.getNextSibling();
                  }
  
                  assertWithMessage("node have same number of children: %s", path)
                          .that(actualChild)
                          .isNull();
              }
              else {
                  final Set<Node> expectedChildren = XmlUtil.getChildrenElements(expected);
                  final Set<Node> actualChildren = XmlUtil.getChildrenElements(actual);
  
                  assertWithMessage("node have same number of children: %s", path)
                          .that(actualChildren)
                          .hasSize(expectedChildren.size());
  
                  for (Node expectedChild : expectedChildren) {
                      Node foundChild = null;
  
                      for (Node actualChild : actualChildren) {
                          if (ordered.test(expectedChild, actualChild)) {
                              foundChild = actualChild;
                              break;
                          }
                      }
  
                      assertWithMessage("node should exist: %s%s/", path, expectedChild.getNodeName())
                              .that(foundChild)
                              .isNotNull();
  
                      verifyXmlNode(expectedChild, foundChild, path, ordered);
                  }
              }
          }
      }
  
      /**
       * Verifies if a xml node in actual xml document and expected xml document match or not based on
       * their name, type, and attributes.
       *
       * @param expected the expected xml node. A {@link Node} interface.
       * @param actual the actual xml node. A {@link Node} interface.
       * @param path the path to the current xml nodes that are compared.
       * @param ordered an ordered predicate for xml nodes.
       */
      private static void verifyXmlNode(Node expected, Node actual, String path,
              BiPredicate<Node, Node> ordered) {
          if (expected == null) {
              if (actual != null) {
                  assertWithMessage("no node should exist: %s%s/", path, actual.getNodeName())
                          .fail();
              }
          }
          else {
              final String newPath = path + expected.getNodeName() + "/";
  
              assertWithMessage("node should exist: %s", newPath)
                      .that(actual)
                      .isNotNull();
              assertWithMessage("node should have same name: %s", newPath)
                      .that(actual.getNodeName())
                      .isEqualTo(expected.getNodeName());
              assertWithMessage("node should have same type: %s", newPath)
                      .that(actual.getNodeType())
                      .isEqualTo(expected.getNodeType());
  
              verifyXmlAttributes(expected.getAttributes(), actual.getAttributes(), newPath);
  
              verifyXmlNodes(expected, actual, newPath, ordered);
          }
      }
  
      /**
       * Verifies xml attributes of collection of actual nodes by comparing with collection
       * of expected node attributes.
       *
       * @param expected collection of expected nodes. A {@link NamedNodeMap} interface.
       * @param actual collection of actual nodes. A {@link NamedNodeMap} interface.
       * @param path the path to these xml nodes that are compared.
       */
      private static void verifyXmlAttributes(NamedNodeMap expected, NamedNodeMap actual,
              String path) {
          if (expected == null) {
              assertWithMessage("no attributes should exist: %s", path)
                      .that(actual)
                      .isNull();
          }
          else {
              assertWithMessage("attributes should exist: %s", path)
                      .that(actual)
                      .isNotNull();
  
              for (int i = 0; i < expected.getLength(); i++) {
                  verifyXmlAttribute(expected.item(i), actual.item(i), path);
              }
  
              assertThat(actual.getLength())
                      .isEqualTo(expected.getLength());
              assertWithMessage("node have same number of attributes: %s", path)
                      .that(actual.getLength())
                      .isEqualTo(expected.getLength());
          }
      }
  
      /**
       * Verifies xml attributes of actual node (like name, and node value) by comparing with
       * expected node attributes.
       *
       * @param expected the expected xml node. A {@link Node} interface.
       * @param actual the actual xml node. A {@link Node} interface.
       * @param path the path to the current xml nodes that are compared.
       */
      private static void verifyXmlAttribute(Node expected, Node actual, String path) {
          final String expectedName = expected.getNodeName();
  
          assertWithMessage("attribute value for '%s' should not be null: %s", expectedName, path)
                  .that(actual)
                  .isNotNull();
  
          assertWithMessage("attribute name should match: %s", path)
                  .that(actual.getNodeName())
                  .isEqualTo(expectedName);
  
          // ignore checkstyle version in xml as it changes each release
          if (!"/#document/checkstyle".equals(path) && !"version".equals(expectedName)) {
              assertWithMessage("attribute value for '%s' should match: %s", expectedName, path)
                      .that(actual.getNodeValue())
                      .isEqualTo(expected.getNodeValue());
          }
      }
  }
  