/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.json; import groovy.lang.Closure; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The original slurper and lexer use this class. * This is kept around in case someone needs its exact behavior. * Enum listing all the possible JSON tokens that should be recognized by the lexer. * * @author Guillaume Laforge * @since 1.8.0 */ public enum JsonTokenType { OPEN_CURLY ( "an openning curly brace '{'", "{" ), CLOSE_CURLY ( "a closing curly brace '}'", "}" ), OPEN_BRACKET ( "an openning square bracket '['", "[" ), CLOSE_BRACKET ( "a closing square bracket ']'", "]" ), COLON ( "a colon ':'", ":" ), COMMA ( "a comma ','", "," ), NULL ( "the constant 'null'", "null" ), TRUE ( "the constant 'true'", "true" ), FALSE ( "the constant 'false'", "false" ), NUMBER ( "a number", Pattern.compile("-?\\d+(\\.\\d+)?((e|E)(\\+|-)?\\d+)?")), //STRING ( "a string", Pattern.compile("\"([^\"\\\\]*|\\\\[\"\\\\bfnrt\\/]|\\\\u[0-9a-fA-F]{4})*\"", Pattern.DOTALL)); /** * Original pattern throws the StackOverflowError for long strings with backslashes. * So it is replaced by a 2-step approach inspired from json2.js sources: * https://github.com/douglascrockford/JSON-js/blob/master/json2.js#L462 * * See JsonTokenTypeTest#testMatchingLongStringWithBackslashes() for details. */ STRING ( "a string", new Closure(null) { private Pattern replacePattern = Pattern.compile("(?:\\\\[\"\\\\bfnrt\\/]|\\\\u[0-9a-fA-F]{4})"); private Pattern validatePattern = Pattern.compile("\"[^\"\\\\]*\""); boolean doCall(String it) { return validatePattern.matcher(replacePattern.matcher(it).replaceAll("@")).matches(); } }); /** * A String constant or a Pattern, serving as a validator for matching tokens. */ private final Object validator; /** * A label describing the token */ private final String label; /** * Construct a token type with a label and a validator * * @param label a label describing the token * @param validator a String or Pattern validating input strings as valid tokens */ JsonTokenType(String label, Object validator) { this.validator = validator; this.label = label; } /** * Tells if an input string matches a token. * * @param input the input string to match * * @return a <code>Matching</code> enum value: * <code>YES</code> if this is an exact match, * <code>POSSIBLE</code> if more characters could turn the input string into a valid token, * or <code>NO</code> if the string cannot possibly match the pattern even with more characters to read. */ public boolean matching(String input) { if (validator instanceof Pattern) { Matcher matcher = ((Pattern) validator).matcher(input); return matcher.matches(); } else if (validator instanceof Closure) { return (Boolean) ((Closure) validator).call(input); } else if (validator instanceof String) { return input.equals(validator); } else { return false; } } /** * Find which JSON value might be starting with a given character * * @param c the character * @return the possible token type found */ public static JsonTokenType startingWith(char c) { switch (c) { case '{': return OPEN_CURLY; case '}': return CLOSE_CURLY; case '[': return OPEN_BRACKET; case ']': return CLOSE_BRACKET; case ',': return COMMA; case ':': return COLON; case 't': return TRUE; case 'f': return FALSE; case 'n': return NULL; case '"': return STRING; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return NUMBER; } return null; } public String getLabel() { return label; } public Object getValidator() { return validator; } }