content: package alien4cloud.test.utils;
  
  import java.io.IOException;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.Set;
  
  import lombok.extern.slf4j.Slf4j;
  import alien4cloud.utils.YamlParserUtil;
  
  import com.fasterxml.jackson.annotation.JsonInclude;
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.SerializationFeature;
  import com.google.common.base.Objects;
  import com.google.common.collect.Lists;
  import com.google.common.collect.Sets;
  
  @Slf4j
  public class YamlJsonAssert {
  
      public static class YamlJsonNotEqualsException extends RuntimeException {
  
          private static final long serialVersionUID = -4821764413592118483L;
  
          public YamlJsonNotEqualsException(String message, Throwable cause) {
              super(message, cause);
          }
  
          public YamlJsonNotEqualsException(String message, String expected, String actual) {
              super("Expected : [" + expected + "], but actual is [" + actual + "], " + message);
          }
  
          public YamlJsonNotEqualsException(String message) {
              super(message);
          }
      }
  
      public static enum DocumentType {
          JSON, YAML
      }
  
      private static final ObjectMapper YAML_OBJECT_MAPPER = YamlParserUtil.createYamlObjectMapper();
  
      private static final ObjectMapper JSON_OBJECT_MAPPER = newJsonObjectMapper();
  
      private static ObjectMapper newJsonObjectMapper() {
          ObjectMapper mapper = new ObjectMapper();
          mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
          mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
          mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
          return mapper;
      }
  
      public static void assertEquals(String expected, String actual, DocumentType documentType) throws IOException, YamlJsonNotEqualsException {
          assertEquals(expected, actual, null, documentType);
      }
  
      public static void assertEquals(String expected, String actual, Set<String> ignoredPaths, DocumentType documentType) throws IOException,
              YamlJsonNotEqualsException {
          ObjectMapper mapper;
          switch (documentType) {
          case JSON:
              mapper = JSON_OBJECT_MAPPER;
              break;
          case YAML:
              mapper = YAML_OBJECT_MAPPER;
              break;
          default:
              throw new IllegalArgumentException("Unsupported document type [" + documentType + "]");
          }
          JsonNode expectedNode = mapper.readTree(expected);
          JsonNode actualNode = mapper.readTree(actual);
          LinkedList<String> path = new LinkedList<String>();
          path.add("/");
          assertEquals(expectedNode, actualNode, path, ignoredPaths);
      }
  
      private static boolean isIgnored(String path, Set<String> ignoredPaths) {
          if (ignoredPaths != null) {
              for (String ignoredPath : ignoredPaths) {
                  if (path.matches(ignoredPath)) {
                      return true;
                  }
              }
          }
          return false;
      }
  
      private static void fail(String message, String currentPath, Set<String> ignoredPaths) {
          if (!isIgnored(currentPath, ignoredPaths)) {
              throw new YamlJsonNotEqualsException(message);
          } else {
              log.warn(message);
          }
      }
  
      private static void equals(String message, String currentPath, Set<String> ignoredPaths, String expected, String actual) {
          if (!isIgnored(currentPath, ignoredPaths)) {
              if (!Objects.equal(expected, actual)) {
                  throw new YamlJsonNotEqualsException(message, expected, actual);
              }
          } else {
              log.warn("Ignoring value comparison for path [" + currentPath + "], expected = [" + expected + "], actual = [" + actual + "]");
          }
      }
  
      private static void assertArrayEquals(JsonNode expected, JsonNode actual, LinkedList<String> path, Set<String> ignoredPaths) {
          if (expected.isArray()) {
              // Expected is also an array try to compare
              Iterator<JsonNode> expectedElements = expected.elements();
              Iterator<JsonNode> actualElements = actual.elements();
              int arrayIndex = 0;
              // When comparing array must respect the same order, is this too strict ?
              while (actualElements.hasNext()) {
                  JsonNode actualElement = actualElements.next();
                  path.push("[" + arrayIndex + "]");
                  String currentPath = getPrettyPath(path);
                  if (expectedElements.hasNext()) {
                      JsonNode expectedElement = expectedElements.next();
                      // Compare array element
                      assertEquals(expectedElement, actualElement, path, ignoredPaths);
                  } else {
                      String message = buildErrorMessage(currentPath, "Actual array contains more elements than expected");
                      fail(message, currentPath, ignoredPaths);
                  }
                  path.pop();
                  arrayIndex++;
              }
          } else {
              String currentPath = getPrettyPath(path);
              String message = buildErrorMessage(currentPath, "Not expecting array at this level");
              fail(message, currentPath, ignoredPaths);
          }
      }
  
      private static void assertValueEquals(JsonNode expected, JsonNode actual, LinkedList<String> path, Set<String> ignoredPaths) {
          if (expected.isValueNode()) {
              String expectedValue = expected.asText();
              String actualValue = actual.asText();
              String currentPath = getPrettyPath(path);
              String message = buildErrorMessage(currentPath, "Value not equals ");
              if (log.isDebugEnabled()) {
                  log.debug(currentPath + " : Comparing value expected = [" + expectedValue + "], actual = [" + actualValue + "]");
              }
              equals(message, currentPath, ignoredPaths, expectedValue, actualValue);
          } else {
              throw new YamlJsonNotEqualsException(buildErrorMessage(getPrettyPath(path), "Expecting keys : " + Sets.newHashSet(expected.fieldNames())
                      + " but not found"));
          }
      }
  
      private static void assertTreeEquals(JsonNode expected, JsonNode actual, LinkedList<String> path, Set<String> ignoredPaths) {
          // If we are here it means it's not an array
          // Getting children name
          Set<String> expectedFieldSet = Sets.newHashSet(expected.fieldNames());
          Set<String> actualFieldSet = Sets.newHashSet(actual.fieldNames());
          for (String actualField : actualFieldSet) {
              path.push(actualField);
              if (!expectedFieldSet.contains(actualField)) {
                  String currentPath = getPrettyPath(path);
                  String message = buildErrorMessage(currentPath, "Not expecting keys : [" + actualField + "]");
                  fail(message, currentPath, ignoredPaths);
              } else {
                  assertEquals(expected.get(actualField), actual.get(actualField), path, ignoredPaths);
              }
              path.pop();
          }
          // Remove all actual to check if there's not found children on actual
          expectedFieldSet.removeAll(actualFieldSet);
          if (expectedFieldSet.size() > 0) {
              for (String expectedField : expectedFieldSet) {
                  path.push(expectedField);
                  String currentPath = getPrettyPath(path);
                  String message = buildErrorMessage(getPrettyPath(path), "Expecting keys : " + expectedFieldSet + " but not found");
                  fail(message, currentPath, ignoredPaths);
                  path.pop();
              }
          }
      }
  
      private static void failNotValueNode(JsonNode expected, JsonNode actual, LinkedList<String> path, Set<String> ignoredPaths) {
          // Expected is a value node, we check if all actual children is ignored
          if (expected.isNull() || (expected.isTextual() && expected.asText() == null) || (expected.isTextual() && expected.asText().isEmpty())) {
              // If we are here 1. expected is null or 2. expected is text and is null 3. expected is text but empty
              Set<String> actualFieldSet = Sets.newHashSet(actual.fieldNames());
              for (String actualField : actualFieldSet) {
                  path.push(actualField);
                  String currentPath = getPrettyPath(path);
                  String message = buildErrorMessage(getPrettyPath(path), "Not expecting keys : [" + actualField + " ]");
                  fail(message, currentPath, ignoredPaths);
                  path.pop();
              }
          } else {
              // Expected is not null, it might be a real problem except if the parent path is ignored
              String currentPath = getPrettyPath(path);
              String message = buildErrorMessage(getPrettyPath(path), "Not expecting keys : [" + Sets.newHashSet(actual.fieldNames()) + " ]");
              fail(message, currentPath, ignoredPaths);
          }
      }
  
      private static void assertEquals(JsonNode expected, JsonNode actual, LinkedList<String> path, Set<String> ignoredPaths) {
          // Check if it's array node
          if (actual.isArray()) {
              // Assert that two array are equals
              assertArrayEquals(expected, actual, path, ignoredPaths);
              return;
          }
  
          // Check if it's value node
          if (actual.isValueNode()) {
              // Assert that two values are equals
              assertValueEquals(expected, actual, path, ignoredPaths);
              return;
          }
  
          // Actual is not a value node, may expected be a value node, we must check that
          if (expected.isValueNode()) {
              // Actual is not a value node, expected is a value node it's an erroneous case
              failNotValueNode(expected, actual, path, ignoredPaths);
              return;
          }
  
          // If we reach here it means both are tree nodes
          assertTreeEquals(expected, actual, path, ignoredPaths);
      }
  
      private static String getPrettyPath(LinkedList<String> pathStack) {
          StringBuilder buffer = new StringBuilder();
          boolean slashAppended = false;
          for (String path : Lists.reverse(pathStack)) {
              buffer.append(path);
              if (!path.equals("/")) {
                  buffer.append('/');
                  // Too ugly
                  slashAppended = true;
              }
          }
          if (slashAppended) {
              buffer.setLength(buffer.length() - 1);
          }
          return buffer.toString();
      }
  
      private static String buildErrorMessage(String path, String customMessage) {
          return "Current path where assertion failed '" + path + "' : " + customMessage;
      }
  }
  