001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.model.util; 028 029import com.fasterxml.jackson.core.JsonLocation; 030import com.fasterxml.jackson.core.JsonParser; 031import com.fasterxml.jackson.core.JsonToken; 032 033import gov.nist.secauto.metaschema.core.util.CustomCollectors; 034import gov.nist.secauto.metaschema.core.util.ObjectUtils; 035 036import org.apache.logging.log4j.LogManager; 037import org.apache.logging.log4j.Logger; 038import org.json.JSONObject; 039import org.json.JSONTokener; 040 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.Reader; 044import java.util.Arrays; 045import java.util.Collection; 046import java.util.List; 047 048import edu.umd.cs.findbugs.annotations.NonNull; 049import edu.umd.cs.findbugs.annotations.Nullable; 050 051public final class JsonUtil { 052 private static final Logger LOGGER = LogManager.getLogger(JsonUtil.class); 053 054 private JsonUtil() { 055 // disable construction 056 } 057 058 @NonNull 059 public static JSONObject toJsonObject(@NonNull InputStream schemaInputStream) { 060 return new JSONObject(new JSONTokener(schemaInputStream)); 061 } 062 063 @NonNull 064 public static JSONObject toJsonObject(@NonNull Reader reader) { 065 return new JSONObject(new JSONTokener(reader)); 066 } 067 068 /** 069 * Generate an informational string describing the token at the current location 070 * of the provided {@code parser}. 071 * 072 * @param parser 073 * the JSON parser 074 * @return the informational string 075 * @throws IOException 076 * if an error occurred while getting the information from the parser 077 */ 078 @SuppressWarnings("null") 079 @NonNull 080 public static String toString(@NonNull JsonParser parser) throws IOException { 081 return new StringBuilder(32) 082 .append(parser.currentToken().name()) 083 .append(" '") 084 .append(parser.getText()) 085 .append('\'') 086 .append(generateLocationMessage(parser)) 087 .toString(); 088 } 089 090 /** 091 * Generate an informational string describing the provided {@code location}. 092 * 093 * @param location 094 * a JSON parser location 095 * @return the informational string 096 */ 097 @SuppressWarnings("null") 098 @NonNull 099 public static String toString(@NonNull JsonLocation location) { 100 return new StringBuilder(8) 101 .append(location.getLineNr()) 102 .append(':') 103 .append(location.getColumnNr()) 104 .toString(); 105 } 106 107 @Nullable 108 public static JsonToken advanceTo(@NonNull JsonParser parser, JsonToken token) throws IOException { 109 JsonToken currentToken = null; 110 while (parser.hasCurrentToken() && !token.equals(currentToken = parser.currentToken())) { 111 currentToken = parser.nextToken(); 112 if (LOGGER.isWarnEnabled()) { 113 LOGGER.warn("skipping over: {}{}", 114 toString(parser), 115 generateLocationMessage(parser)); 116 } 117 } 118 return currentToken; 119 } 120 121 @SuppressWarnings({ 122 "resource", // parser not owned 123 "PMD.CyclomaticComplexity" // acceptable 124 }) 125 @Nullable 126 public static JsonToken skipNextValue(@NonNull JsonParser parser) throws IOException { 127 128 JsonToken currentToken = parser.currentToken(); 129 // skip the field name 130 if (JsonToken.FIELD_NAME.equals(currentToken)) { 131 currentToken = parser.nextToken(); 132 } 133 134 switch (currentToken) { 135 case START_ARRAY: 136 case START_OBJECT: 137 parser.skipChildren(); 138 break; 139 case VALUE_FALSE: 140 case VALUE_NULL: 141 case VALUE_NUMBER_FLOAT: 142 case VALUE_NUMBER_INT: 143 case VALUE_STRING: 144 case VALUE_TRUE: 145 // do nothing 146 break; 147 default: 148 // error 149 String msg = String.format("Unhandled JsonToken %s%s.", 150 toString(parser), 151 generateLocationMessage(parser)); 152 LOGGER.error(msg); 153 throw new UnsupportedOperationException(msg); 154 } 155 156 // advance past the value 157 return parser.nextToken(); 158 } 159 160 @SuppressWarnings("PMD.CyclomaticComplexity") // acceptable 161 public static boolean checkEndOfValue(@NonNull JsonParser parser, @NonNull JsonToken startToken) { 162 JsonToken currentToken = parser.getCurrentToken(); 163 164 boolean retval; 165 switch (startToken) { // NOPMD - intentional fall through 166 case START_OBJECT: 167 retval = JsonToken.END_OBJECT.equals(currentToken); 168 break; 169 case START_ARRAY: 170 retval = JsonToken.END_ARRAY.equals(currentToken); 171 break; 172 case VALUE_EMBEDDED_OBJECT: 173 case VALUE_FALSE: 174 case VALUE_NULL: 175 case VALUE_NUMBER_FLOAT: 176 case VALUE_NUMBER_INT: 177 case VALUE_STRING: 178 case VALUE_TRUE: 179 retval = true; 180 break; 181 default: 182 retval = false; 183 } 184 return retval; 185 } 186 187 public static void assertCurrent( 188 @NonNull JsonParser parser, 189 @NonNull JsonToken... expectedTokens) { 190 JsonToken current = parser.currentToken(); 191 assert Arrays.stream(expectedTokens) 192 .anyMatch(expected -> expected.equals(current)) : getAssertMessage( 193 parser, 194 expectedTokens, 195 parser.currentToken()); 196 } 197 198 public static void assertCurrentIsFieldValue(@NonNull JsonParser parser) { 199 JsonToken token = parser.currentToken(); 200 assert token.isStructStart() || token.isScalarValue() : String.format( 201 "Expected a START_OBJECT, START_ARRAY, or VALUE_xxx token, but found JsonToken '%s'%s.", 202 token, 203 generateLocationMessage(parser)); 204 } 205 206 @Nullable 207 public static JsonToken assertAndAdvance( 208 @NonNull JsonParser parser, 209 @NonNull JsonToken expectedToken) 210 throws IOException { 211 JsonToken token = parser.currentToken(); 212 assert expectedToken.equals(token) : getAssertMessage( 213 parser, 214 expectedToken, 215 token); 216 return parser.nextToken(); 217 } 218 219 @Nullable 220 public static JsonToken advanceAndAssert( 221 @NonNull JsonParser parser, 222 @NonNull JsonToken expectedToken) 223 throws IOException { 224 JsonToken token = parser.nextToken(); 225 assert expectedToken.equals(token) : getAssertMessage( 226 parser, 227 expectedToken, 228 token); 229 return token; 230 } 231 232 @NonNull 233 public static String getAssertMessage( 234 @NonNull JsonParser parser, 235 @NonNull JsonToken expected, 236 JsonToken actual) { 237 return ObjectUtils.notNull( 238 String.format("Expected JsonToken '%s', but found JsonToken '%s'%s.", 239 expected, 240 actual, 241 generateLocationMessage(parser))); 242 } 243 244 @NonNull 245 public static String getAssertMessage( 246 @NonNull JsonParser parser, 247 @NonNull JsonToken[] expected, 248 JsonToken actual) { 249 List<JsonToken> expectedTokens = ObjectUtils.notNull(Arrays.asList(expected)); 250 return getAssertMessage(parser, expectedTokens, actual); 251 } 252 253 @NonNull 254 public static String getAssertMessage( 255 @NonNull JsonParser parser, 256 @NonNull Collection<JsonToken> expected, 257 JsonToken actual) { 258 return ObjectUtils.notNull( 259 String.format("Expected JsonToken(s) '%s', but found JsonToken '%s'%s.", 260 expected.stream().map(token -> token.name()).collect(CustomCollectors.joiningWithOxfordComma("and")), 261 actual, 262 generateLocationMessage(parser))); 263 } 264 265 @NonNull 266 public static CharSequence generateLocationMessage(@NonNull JsonParser parser) { 267 JsonLocation location = parser.getCurrentLocation(); 268 return location == null ? "" : generateLocationMessage(location); 269 } 270 271 @SuppressWarnings("null") 272 @NonNull 273 public static CharSequence generateLocationMessage(@NonNull JsonLocation location) { 274 return new StringBuilder() 275 .append(" at location '") 276 .append(location.getLineNr()) 277 .append(':') 278 .append(location.getColumnNr()) 279 .append('\''); 280 } 281}