JsonUtil.java

  1. /*
  2.  * Portions of this software was developed by employees of the National Institute
  3.  * of Standards and Technology (NIST), an agency of the Federal Government and is
  4.  * being made available as a public service. Pursuant to title 17 United States
  5.  * Code Section 105, works of NIST employees are not subject to copyright
  6.  * protection in the United States. This software may be subject to foreign
  7.  * copyright. Permission in the United States and in foreign countries, to the
  8.  * extent that NIST may hold copyright, to use, copy, modify, create derivative
  9.  * works, and distribute this software and its documentation without fee is hereby
  10.  * granted on a non-exclusive basis, provided that this notice and disclaimer
  11.  * of warranty appears in all copies.
  12.  *
  13.  * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
  14.  * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
  15.  * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
  16.  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
  17.  * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
  18.  * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
  19.  * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
  20.  * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
  21.  * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
  22.  * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
  23.  * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
  24.  * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
  25.  */

  26. package gov.nist.secauto.metaschema.core.model.util;

  27. import com.fasterxml.jackson.core.JsonLocation;
  28. import com.fasterxml.jackson.core.JsonParser;
  29. import com.fasterxml.jackson.core.JsonToken;

  30. import gov.nist.secauto.metaschema.core.util.CustomCollectors;
  31. import gov.nist.secauto.metaschema.core.util.ObjectUtils;

  32. import org.apache.logging.log4j.LogManager;
  33. import org.apache.logging.log4j.Logger;
  34. import org.json.JSONObject;
  35. import org.json.JSONTokener;

  36. import java.io.IOException;
  37. import java.io.InputStream;
  38. import java.io.Reader;
  39. import java.util.Arrays;
  40. import java.util.Collection;
  41. import java.util.List;

  42. import edu.umd.cs.findbugs.annotations.NonNull;
  43. import edu.umd.cs.findbugs.annotations.Nullable;

  44. public final class JsonUtil {
  45.   private static final Logger LOGGER = LogManager.getLogger(JsonUtil.class);

  46.   private JsonUtil() {
  47.     // disable construction
  48.   }

  49.   @NonNull
  50.   public static JSONObject toJsonObject(@NonNull InputStream schemaInputStream) {
  51.     return new JSONObject(new JSONTokener(schemaInputStream));
  52.   }

  53.   @NonNull
  54.   public static JSONObject toJsonObject(@NonNull Reader reader) {
  55.     return new JSONObject(new JSONTokener(reader));
  56.   }

  57.   /**
  58.    * Generate an informational string describing the token at the current location
  59.    * of the provided {@code parser}.
  60.    *
  61.    * @param parser
  62.    *          the JSON parser
  63.    * @return the informational string
  64.    * @throws IOException
  65.    *           if an error occurred while getting the information from the parser
  66.    */
  67.   @SuppressWarnings("null")
  68.   @NonNull
  69.   public static String toString(@NonNull JsonParser parser) throws IOException {
  70.     return new StringBuilder(32)
  71.         .append(parser.currentToken().name())
  72.         .append(" '")
  73.         .append(parser.getText())
  74.         .append('\'')
  75.         .append(generateLocationMessage(parser))
  76.         .toString();
  77.   }

  78.   /**
  79.    * Generate an informational string describing the provided {@code location}.
  80.    *
  81.    * @param location
  82.    *          a JSON parser location
  83.    * @return the informational string
  84.    */
  85.   @SuppressWarnings("null")
  86.   @NonNull
  87.   public static String toString(@NonNull JsonLocation location) {
  88.     return new StringBuilder(8)
  89.         .append(location.getLineNr())
  90.         .append(':')
  91.         .append(location.getColumnNr())
  92.         .toString();
  93.   }

  94.   @Nullable
  95.   public static JsonToken advanceTo(@NonNull JsonParser parser, JsonToken token) throws IOException {
  96.     JsonToken currentToken = null;
  97.     while (parser.hasCurrentToken() && !token.equals(currentToken = parser.currentToken())) {
  98.       currentToken = parser.nextToken();
  99.       if (LOGGER.isWarnEnabled()) {
  100.         LOGGER.warn("skipping over: {}{}",
  101.             toString(parser),
  102.             generateLocationMessage(parser));
  103.       }
  104.     }
  105.     return currentToken;
  106.   }

  107.   @SuppressWarnings({
  108.       "resource", // parser not owned
  109.       "PMD.CyclomaticComplexity" // acceptable
  110.   })
  111.   @Nullable
  112.   public static JsonToken skipNextValue(@NonNull JsonParser parser) throws IOException {

  113.     JsonToken currentToken = parser.currentToken();
  114.     // skip the field name
  115.     if (JsonToken.FIELD_NAME.equals(currentToken)) {
  116.       currentToken = parser.nextToken();
  117.     }

  118.     switch (currentToken) {
  119.     case START_ARRAY:
  120.     case START_OBJECT:
  121.       parser.skipChildren();
  122.       break;
  123.     case VALUE_FALSE:
  124.     case VALUE_NULL:
  125.     case VALUE_NUMBER_FLOAT:
  126.     case VALUE_NUMBER_INT:
  127.     case VALUE_STRING:
  128.     case VALUE_TRUE:
  129.       // do nothing
  130.       break;
  131.     default:
  132.       // error
  133.       String msg = String.format("Unhandled JsonToken %s%s.",
  134.           toString(parser),
  135.           generateLocationMessage(parser));
  136.       LOGGER.error(msg);
  137.       throw new UnsupportedOperationException(msg);
  138.     }

  139.     // advance past the value
  140.     return parser.nextToken();
  141.   }

  142.   @SuppressWarnings("PMD.CyclomaticComplexity") // acceptable
  143.   public static boolean checkEndOfValue(@NonNull JsonParser parser, @NonNull JsonToken startToken) {
  144.     JsonToken currentToken = parser.getCurrentToken();

  145.     boolean retval;
  146.     switch (startToken) { // NOPMD - intentional fall through
  147.     case START_OBJECT:
  148.       retval = JsonToken.END_OBJECT.equals(currentToken);
  149.       break;
  150.     case START_ARRAY:
  151.       retval = JsonToken.END_ARRAY.equals(currentToken);
  152.       break;
  153.     case VALUE_EMBEDDED_OBJECT:
  154.     case VALUE_FALSE:
  155.     case VALUE_NULL:
  156.     case VALUE_NUMBER_FLOAT:
  157.     case VALUE_NUMBER_INT:
  158.     case VALUE_STRING:
  159.     case VALUE_TRUE:
  160.       retval = true;
  161.       break;
  162.     default:
  163.       retval = false;
  164.     }
  165.     return retval;
  166.   }

  167.   public static void assertCurrent(
  168.       @NonNull JsonParser parser,
  169.       @NonNull JsonToken... expectedTokens) {
  170.     JsonToken current = parser.currentToken();
  171.     assert Arrays.stream(expectedTokens)
  172.         .anyMatch(expected -> expected.equals(current)) : getAssertMessage(
  173.             parser,
  174.             expectedTokens,
  175.             parser.currentToken());
  176.   }

  177.   public static void assertCurrentIsFieldValue(@NonNull JsonParser parser) {
  178.     JsonToken token = parser.currentToken();
  179.     assert token.isStructStart() || token.isScalarValue() : String.format(
  180.         "Expected a START_OBJECT, START_ARRAY, or VALUE_xxx token, but found JsonToken '%s'%s.",
  181.         token,
  182.         generateLocationMessage(parser));
  183.   }

  184.   @Nullable
  185.   public static JsonToken assertAndAdvance(
  186.       @NonNull JsonParser parser,
  187.       @NonNull JsonToken expectedToken)
  188.       throws IOException {
  189.     JsonToken token = parser.currentToken();
  190.     assert expectedToken.equals(token) : getAssertMessage(
  191.         parser,
  192.         expectedToken,
  193.         token);
  194.     return parser.nextToken();
  195.   }

  196.   @Nullable
  197.   public static JsonToken advanceAndAssert(
  198.       @NonNull JsonParser parser,
  199.       @NonNull JsonToken expectedToken)
  200.       throws IOException {
  201.     JsonToken token = parser.nextToken();
  202.     assert expectedToken.equals(token) : getAssertMessage(
  203.         parser,
  204.         expectedToken,
  205.         token);
  206.     return token;
  207.   }

  208.   @NonNull
  209.   public static String getAssertMessage(
  210.       @NonNull JsonParser parser,
  211.       @NonNull JsonToken expected,
  212.       JsonToken actual) {
  213.     return ObjectUtils.notNull(
  214.         String.format("Expected JsonToken '%s', but found JsonToken '%s'%s.",
  215.             expected,
  216.             actual,
  217.             generateLocationMessage(parser)));
  218.   }

  219.   @NonNull
  220.   public static String getAssertMessage(
  221.       @NonNull JsonParser parser,
  222.       @NonNull JsonToken[] expected,
  223.       JsonToken actual) {
  224.     List<JsonToken> expectedTokens = ObjectUtils.notNull(Arrays.asList(expected));
  225.     return getAssertMessage(parser, expectedTokens, actual);
  226.   }

  227.   @NonNull
  228.   public static String getAssertMessage(
  229.       @NonNull JsonParser parser,
  230.       @NonNull Collection<JsonToken> expected,
  231.       JsonToken actual) {
  232.     return ObjectUtils.notNull(
  233.         String.format("Expected JsonToken(s) '%s', but found JsonToken '%s'%s.",
  234.             expected.stream().map(token -> token.name()).collect(CustomCollectors.joiningWithOxfordComma("and")),
  235.             actual,
  236.             generateLocationMessage(parser)));
  237.   }

  238.   @NonNull
  239.   public static CharSequence generateLocationMessage(@NonNull JsonParser parser) {
  240.     JsonLocation location = parser.getCurrentLocation();
  241.     return location == null ? "" : generateLocationMessage(location);
  242.   }

  243.   @SuppressWarnings("null")
  244.   @NonNull
  245.   public static CharSequence generateLocationMessage(@NonNull JsonLocation location) {
  246.     return new StringBuilder()
  247.         .append(" at location '")
  248.         .append(location.getLineNr())
  249.         .append(':')
  250.         .append(location.getColumnNr())
  251.         .append('\'');
  252.   }
  253. }