blob: 1e5f6d0e0a078a9db377c10699e403aab517d4e5 [file] [log] [blame]
package org.codehaus.jackson.impl;
import java.io.IOException;
import org.codehaus.jackson.*;
import org.codehaus.jackson.JsonParser.Feature;
import org.codehaus.jackson.io.NumberInput;
import org.codehaus.jackson.util.ByteArrayBuilder;
/**
* Intermediate base class used by all Jackson {@link JsonParser}
* implementations, but does not add any additional fields that depend
* on particular method of obtaining input.
*<p>
* Note that 'minimal' here mostly refers to minimal number of fields
* (size) and functionality that is specific to certain types
* of parser implementations; but not necessarily to number of methods.
*
* @since 1.6
*
* @author Tatu Saloranta
*/
public abstract class JsonParserMinimalBase
extends JsonParser
{
// Control chars:
protected final static int INT_TAB = '\t';
protected final static int INT_LF = '\n';
protected final static int INT_CR = '\r';
protected final static int INT_SPACE = 0x0020;
// Markup
protected final static int INT_LBRACKET = '[';
protected final static int INT_RBRACKET = ']';
protected final static int INT_LCURLY = '{';
protected final static int INT_RCURLY = '}';
protected final static int INT_QUOTE = '"';
protected final static int INT_BACKSLASH = '\\';
protected final static int INT_SLASH = '/';
protected final static int INT_COLON = ':';
protected final static int INT_COMMA = ',';
protected final static int INT_ASTERISK = '*';
protected final static int INT_APOSTROPHE = '\'';
// Letters we need
protected final static int INT_b = 'b';
protected final static int INT_f = 'f';
protected final static int INT_n = 'n';
protected final static int INT_r = 'r';
protected final static int INT_t = 't';
protected final static int INT_u = 'u';
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
protected JsonParserMinimalBase() { }
protected JsonParserMinimalBase(int features) {
super(features);
}
/*
/**********************************************************
/* Configuration overrides if any
/**********************************************************
*/
// from base class:
//public void enableFeature(Feature f)
//public void disableFeature(Feature f)
//public void setFeature(Feature f, boolean state)
//public boolean isFeatureEnabled(Feature f)
/*
/**********************************************************
/* JsonParser impl
/**********************************************************
*/
@Override
public abstract JsonToken nextToken() throws IOException, JsonParseException;
//public final JsonToken nextValue()
@Override
public JsonParser skipChildren() throws IOException, JsonParseException
{
if (_currToken != JsonToken.START_OBJECT
&& _currToken != JsonToken.START_ARRAY) {
return this;
}
int open = 1;
/* Since proper matching of start/end markers is handled
* by nextToken(), we'll just count nesting levels here
*/
while (true) {
JsonToken t = nextToken();
if (t == null) {
_handleEOF();
/* given constraints, above should never return;
* however, FindBugs doesn't know about it and
* complains... so let's add dummy break here
*/
return this;
}
switch (t) {
case START_OBJECT:
case START_ARRAY:
++open;
break;
case END_OBJECT:
case END_ARRAY:
if (--open == 0) {
return this;
}
break;
}
}
}
/**
* Method sub-classes need to implement
*/
protected abstract void _handleEOF() throws JsonParseException;
//public JsonToken getCurrentToken()
//public boolean hasCurrentToken()
@Override
public abstract String getCurrentName() throws IOException, JsonParseException;
@Override
public abstract void close() throws IOException;
@Override
public abstract boolean isClosed();
@Override
public abstract JsonStreamContext getParsingContext();
// public abstract JsonLocation getTokenLocation();
// public abstract JsonLocation getCurrentLocation();
/*
/**********************************************************
/* Public API, access to token information, text
/**********************************************************
*/
@Override
public abstract String getText() throws IOException, JsonParseException;
@Override
public abstract char[] getTextCharacters() throws IOException, JsonParseException;
@Override
public abstract boolean hasTextCharacters();
@Override
public abstract int getTextLength() throws IOException, JsonParseException;
@Override
public abstract int getTextOffset() throws IOException, JsonParseException;
/*
/**********************************************************
/* Public API, access to token information, binary
/**********************************************************
*/
@Override
public abstract byte[] getBinaryValue(Base64Variant b64variant)
throws IOException, JsonParseException;
/*
/**********************************************************
/* Public API, access with conversion/coercion
/**********************************************************
*/
@Override
public boolean getValueAsBoolean(boolean defaultValue) throws IOException, JsonParseException
{
if (_currToken != null) {
switch (_currToken) {
case VALUE_NUMBER_INT:
return getIntValue() != 0;
case VALUE_TRUE:
return true;
case VALUE_FALSE:
case VALUE_NULL:
return false;
case VALUE_EMBEDDED_OBJECT:
{
Object value = this.getEmbeddedObject();
if (value instanceof Boolean) {
return ((Boolean) value).booleanValue();
}
}
case VALUE_STRING:
String str = getText().trim();
if ("true".equals(str)) {
return true;
}
break;
}
}
return defaultValue;
}
@Override
public int getValueAsInt(int defaultValue) throws IOException, JsonParseException
{
if (_currToken != null) {
switch (_currToken) {
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
return getIntValue();
case VALUE_TRUE:
return 1;
case VALUE_FALSE:
case VALUE_NULL:
return 0;
case VALUE_STRING:
return NumberInput.parseAsInt(getText(), defaultValue);
case VALUE_EMBEDDED_OBJECT:
{
Object value = this.getEmbeddedObject();
if (value instanceof Number) {
return ((Number) value).intValue();
}
}
}
}
return defaultValue;
}
@Override
public long getValueAsLong(long defaultValue) throws IOException, JsonParseException
{
if (_currToken != null) {
switch (_currToken) {
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
return getLongValue();
case VALUE_TRUE:
return 1;
case VALUE_FALSE:
case VALUE_NULL:
return 0;
case VALUE_STRING:
return NumberInput.parseAsLong(getText(), defaultValue);
case VALUE_EMBEDDED_OBJECT:
{
Object value = this.getEmbeddedObject();
if (value instanceof Number) {
return ((Number) value).longValue();
}
}
}
}
return defaultValue;
}
@Override
public double getValueAsDouble(double defaultValue) throws IOException, JsonParseException
{
if (_currToken != null) {
switch (_currToken) {
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
return getDoubleValue();
case VALUE_TRUE:
return 1;
case VALUE_FALSE:
case VALUE_NULL:
return 0;
case VALUE_STRING:
return NumberInput.parseAsDouble(getText(), defaultValue);
case VALUE_EMBEDDED_OBJECT:
{
Object value = this.getEmbeddedObject();
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
}
}
}
return defaultValue;
}
/*
/**********************************************************
/* Base64 decoding
/**********************************************************
*/
/**
* Helper method that can be used for base64 decoding in cases where
* encoded content has already been read as a String.
*
* @since 1.9.3
*/
protected void _decodeBase64(String str, ByteArrayBuilder builder, Base64Variant b64variant)
throws IOException, JsonParseException
{
int ptr = 0;
int len = str.length();
main_loop:
while (ptr < len) {
// first, we'll skip preceding white space, if any
char ch;
do {
ch = str.charAt(ptr++);
if (ptr >= len) {
break main_loop;
}
} while (ch <= INT_SPACE);
int bits = b64variant.decodeBase64Char(ch);
if (bits < 0) {
_reportInvalidBase64(b64variant, ch, 0, null);
}
int decodedData = bits;
// then second base64 char; can't get padding yet, nor ws
if (ptr >= len) {
_reportBase64EOF();
}
ch = str.charAt(ptr++);
bits = b64variant.decodeBase64Char(ch);
if (bits < 0) {
_reportInvalidBase64(b64variant, ch, 1, null);
}
decodedData = (decodedData << 6) | bits;
// third base64 char; can be padding, but not ws
if (ptr >= len) {
// but as per [JACKSON-631] can be end-of-input, iff not using padding
if (!b64variant.usesPadding()) {
decodedData >>= 4;
builder.append(decodedData);
break;
}
_reportBase64EOF();
}
ch = str.charAt(ptr++);
bits = b64variant.decodeBase64Char(ch);
// First branch: can get padding (-> 1 byte)
if (bits < 0) {
if (bits != Base64Variant.BASE64_VALUE_PADDING) {
_reportInvalidBase64(b64variant, ch, 2, null);
}
// Ok, must get padding
if (ptr >= len) {
_reportBase64EOF();
}
ch = str.charAt(ptr++);
if (!b64variant.usesPaddingChar(ch)) {
_reportInvalidBase64(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
}
// Got 12 bits, only need 8, need to shift
decodedData >>= 4;
builder.append(decodedData);
continue;
}
// Nope, 2 or 3 bytes
decodedData = (decodedData << 6) | bits;
// fourth and last base64 char; can be padding, but not ws
if (ptr >= len) {
// but as per [JACKSON-631] can be end-of-input, iff not using padding
if (!b64variant.usesPadding()) {
decodedData >>= 2;
builder.appendTwoBytes(decodedData);
break;
}
_reportBase64EOF();
}
ch = str.charAt(ptr++);
bits = b64variant.decodeBase64Char(ch);
if (bits < 0) {
if (bits != Base64Variant.BASE64_VALUE_PADDING) {
_reportInvalidBase64(b64variant, ch, 3, null);
}
decodedData >>= 2;
builder.appendTwoBytes(decodedData);
} else {
// otherwise, our triple is now complete
decodedData = (decodedData << 6) | bits;
builder.appendThreeBytes(decodedData);
}
}
}
/**
* @param bindex Relative index within base64 character unit; between 0
* and 3 (as unit has exactly 4 characters)
*/
protected void _reportInvalidBase64(Base64Variant b64variant, char ch, int bindex, String msg)
throws JsonParseException
{
String base;
if (ch <= INT_SPACE) {
base = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units";
} else if (b64variant.usesPaddingChar(ch)) {
base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character";
} else if (!Character.isDefined(ch) || Character.isISOControl(ch)) {
// Not sure if we can really get here... ? (most illegal xml chars are caught at lower level)
base = "Illegal character (code 0x"+Integer.toHexString(ch)+") in base64 content";
} else {
base = "Illegal character '"+ch+"' (code 0x"+Integer.toHexString(ch)+") in base64 content";
}
if (msg != null) {
base = base + ": " + msg;
}
throw _constructError(base);
}
protected void _reportBase64EOF() throws JsonParseException {
throw _constructError("Unexpected end-of-String in base64 content");
}
/*
/**********************************************************
/* Error reporting
/**********************************************************
*/
protected void _reportUnexpectedChar(int ch, String comment)
throws JsonParseException
{
String msg = "Unexpected character ("+_getCharDesc(ch)+")";
if (comment != null) {
msg += ": "+comment;
}
_reportError(msg);
}
protected void _reportInvalidEOF()
throws JsonParseException
{
_reportInvalidEOF(" in "+_currToken);
}
protected void _reportInvalidEOF(String msg)
throws JsonParseException
{
_reportError("Unexpected end-of-input"+msg);
}
protected void _reportInvalidEOFInValue() throws JsonParseException
{
_reportInvalidEOF(" in a value");
}
protected void _throwInvalidSpace(int i)
throws JsonParseException
{
char c = (char) i;
String msg = "Illegal character ("+_getCharDesc(c)+"): only regular white space (\\r, \\n, \\t) is allowed between tokens";
_reportError(msg);
}
/**
* Method called to report a problem with unquoted control character.
* Note: starting with version 1.4, it is possible to suppress
* exception by enabling {@link Feature#ALLOW_UNQUOTED_CONTROL_CHARS}.
*/
protected void _throwUnquotedSpace(int i, String ctxtDesc)
throws JsonParseException
{
// JACKSON-208; possible to allow unquoted control chars:
if (!isEnabled(Feature.ALLOW_UNQUOTED_CONTROL_CHARS) || i >= INT_SPACE) {
char c = (char) i;
String msg = "Illegal unquoted character ("+_getCharDesc(c)+"): has to be escaped using backslash to be included in "+ctxtDesc;
_reportError(msg);
}
}
protected char _handleUnrecognizedCharacterEscape(char ch) throws JsonProcessingException
{
// as per [JACKSON-300]
if (isEnabled(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)) {
return ch;
}
// and [JACKSON-548]
if (ch == '\'' && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) {
return ch;
}
_reportError("Unrecognized character escape "+_getCharDesc(ch));
return ch;
}
/*
/**********************************************************
/* Error reporting, generic
/**********************************************************
*/
protected final static String _getCharDesc(int ch)
{
char c = (char) ch;
if (Character.isISOControl(c)) {
return "(CTRL-CHAR, code "+ch+")";
}
if (ch > 255) {
return "'"+c+"' (code "+ch+" / 0x"+Integer.toHexString(ch)+")";
}
return "'"+c+"' (code "+ch+")";
}
protected final void _reportError(String msg)
throws JsonParseException
{
throw _constructError(msg);
}
protected final void _wrapError(String msg, Throwable t)
throws JsonParseException
{
throw _constructError(msg, t);
}
protected final void _throwInternal()
{
throw new RuntimeException("Internal error: this code path should never get executed");
}
protected final JsonParseException _constructError(String msg, Throwable t)
{
return new JsonParseException(msg, getCurrentLocation(), t);
}
}