/*
* 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;
}
}