JavaPropsParser.java
package tools.jackson.dataformat.javaprop;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
import tools.jackson.core.*;
import tools.jackson.core.base.ParserMinimalBase;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.util.ByteArrayBuilder;
import tools.jackson.core.util.JacksonFeatureSet;
import tools.jackson.dataformat.javaprop.io.JPropReadContext;
import tools.jackson.dataformat.javaprop.util.JPropNode;
import tools.jackson.dataformat.javaprop.util.JPropNodeBuilder;
public class JavaPropsParser extends ParserMinimalBase
{
protected final static JavaPropsSchema DEFAULT_SCHEMA = new JavaPropsSchema();
/**
* Properties capabilities slightly different from defaults, having
* untyped (text-only) scalars.
*/
protected final static JacksonFeatureSet<StreamReadCapability> STREAM_READ_CAPABILITIES =
DEFAULT_READ_CAPABILITIES
.with(StreamReadCapability.UNTYPED_SCALARS)
;
/*
/**********************************************************************
/* Configuration
/**********************************************************************
*/
/**
* Although most massaging is done later, caller may be interested in the
* ultimate source.
*/
protected final Object _inputSource;
/**
* Actual {@link java.util.Properties} (or, actually, any {@link java.util.Map}
* with String keys, values) that were parsed and handed to us
* for further processing.
*/
protected final Map<?,?> _sourceContent;
/**
* Schema we use for parsing Properties into structure of some kind.
*/
protected JavaPropsSchema _schema = DEFAULT_SCHEMA;
/*
/**********************************************************************
/* Parsing state
/**********************************************************************
*/
protected JPropReadContext _streamReadContext;
/*
/**********************************************************************
/* Recycled helper objects
/**********************************************************************
*/
protected ByteArrayBuilder _byteArrayBuilder;
protected byte[] _binaryValue;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
public JavaPropsParser(ObjectReadContext readCtxt, IOContext ioCtxt,
int parserFeatures, JavaPropsSchema schema,
Object inputSource, Map<?,?> sourceMap)
{
super(readCtxt, ioCtxt, parserFeatures);
_inputSource = inputSource;
_sourceContent = sourceMap;
_schema = schema;
}
@Override
public Version version() {
return PackageVersion.VERSION;
}
@Override
public JavaPropsSchema getSchema() {
return _schema;
}
/*
/**********************************************************************
/* Public API overrides: input state
/**********************************************************************
*/
// we do not take byte-based input, so base impl would be fine
/*
@Override
public int releaseBuffered(OutputStream out) {
return -1;
}
*/
// current implementation delegates to JDK `Properties, so we don't ever
// see the input so:
/*
@Override
public int releaseBuffered(Writer w) {
return -1;
}
*/
@Override
protected void _closeInput() throws IOException {
_streamReadContext = null;
}
@Override
protected void _releaseBuffers() { }
@Override
public Object streamReadInputSource() {
return _inputSource;
}
/*
/**********************************************************************
/* Public API overrides: capability introspection methods
/**********************************************************************
*/
@Override
public boolean canReadObjectId() { return false; }
@Override
public boolean canReadTypeId() { return false; }
@Override
public JacksonFeatureSet<StreamReadCapability> streamReadCapabilities() {
return STREAM_READ_CAPABILITIES;
}
/*
/**********************************************************************
/* Public API, structural
/**********************************************************************
*/
@Override
public TokenStreamContext streamReadContext() { return _streamReadContext; }
@Override public void assignCurrentValue(Object v) { _streamReadContext.assignCurrentValue(v); }
@Override public Object currentValue() { return _streamReadContext.currentValue(); }
/*
/**********************************************************************
/* Main parsing API, textual values
/**********************************************************************
*/
@Override
public String currentName() {
if (_streamReadContext == null) {
return null;
}
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
JPropReadContext parent = _streamReadContext.getParent();
if (parent != null) {
return parent.currentName();
}
}
return _streamReadContext.currentName();
}
@Override
public JsonToken nextToken() throws JacksonException {
_binaryValue = null;
if (_streamReadContext == null) {
if (_closed) {
return null;
}
JPropNode root = JPropNodeBuilder.build(_sourceContent, _schema);
_streamReadContext = JPropReadContext.create(root);
// 30-Mar-2016, tatu: For debugging can be useful:
/*
System.err.println("SOURCE: ("+root.getClass().getName()+") <<\n"+new ObjectMapper().writerWithDefaultPrettyPrinter()
.writeValueAsString(root.asRaw()));
System.err.println("\n>>");
*/
}
JsonToken t;
while ((t = _streamReadContext.nextToken()) == null) {
_streamReadContext = _streamReadContext.nextContext();
if (_streamReadContext == null) { // end of content
return _updateTokenToNull();
}
streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth());
}
return _updateToken(t);
}
@Override
public String getString() throws JacksonException {
JsonToken t = _currToken;
if (t == JsonToken.VALUE_STRING) {
return _streamReadContext.getCurrentText();
}
if (t == JsonToken.PROPERTY_NAME) {
return _streamReadContext.currentName();
}
// shouldn't have non-String scalar values so:
return (t == null) ? null : t.asString();
}
@Override
public boolean hasStringCharacters() {
return false;
}
@Override
public char[] getStringCharacters() throws JacksonException {
String text = getString();
return (text == null) ? null : text.toCharArray();
}
@Override
public int getStringLength() throws JacksonException {
String text = getString();
return (text == null) ? 0 : text.length();
}
@Override
public int getStringOffset() throws JacksonException {
return 0;
}
@Override
public int getString(Writer writer) throws JacksonException
{
String str = getString();
if (str == null) {
return 0;
}
try {
writer.write(str);
} catch (IOException e) {
throw _wrapIOFailure(e);
}
return str.length();
}
@SuppressWarnings("resource")
@Override
public byte[] getBinaryValue(Base64Variant variant) throws JacksonException
{
if (_binaryValue == null) {
if (_currToken != JsonToken.VALUE_STRING) {
_reportError("Current token ("+_currToken+") not VALUE_STRING, cannot access as binary");
}
ByteArrayBuilder builder = _getByteArrayBuilder();
_decodeBase64(getString(), builder, variant);
_binaryValue = builder.toByteArray();
}
return _binaryValue;
}
public ByteArrayBuilder _getByteArrayBuilder()
{
if (_byteArrayBuilder == null) {
_byteArrayBuilder = new ByteArrayBuilder();
} else {
_byteArrayBuilder.reset();
}
return _byteArrayBuilder;
}
/*
/**********************************************************************
/* Other accessor overrides
/**********************************************************************
*/
@Override
public Object getEmbeddedObject() throws JacksonException {
return null;
}
@Override
public TokenStreamLocation currentTokenLocation() {
return TokenStreamLocation.NA;
}
@Override
public TokenStreamLocation currentLocation() {
return TokenStreamLocation.NA;
}
/*
/**********************************************************************
/* Main parsing API, textual values
/**********************************************************************
*/
@Override
public Number getNumberValue() throws JacksonException {
return _noNumbers();
}
@Override
public NumberType getNumberType() throws JacksonException {
// 01-Jun-2025, tatu: As per [core#1434] 3.x should just return null
// for non-number tokens
return null;
}
@Override
public int getIntValue() throws JacksonException {
return _noNumbers();
}
@Override
public long getLongValue() throws JacksonException {
return _noNumbers();
}
@Override
public BigInteger getBigIntegerValue() throws JacksonException {
return _noNumbers();
}
@Override
public float getFloatValue() throws JacksonException {
return _noNumbers();
}
@Override
public double getDoubleValue() throws JacksonException {
return _noNumbers();
}
@Override
public BigDecimal getDecimalValue() throws JacksonException {
return _noNumbers();
}
@Override
public boolean isNaN() {
return false;
}
/*
/**********************************************************************
/* Internal helper methods
/**********************************************************************
*/
protected <T> T _noNumbers() throws StreamReadException {
_reportError("Current token ("+_currToken+") not numeric, cannot use numeric value accessors");
return null;
}
@Override
protected void _handleEOF() throws StreamReadException {
if ((_streamReadContext != null) && !_streamReadContext.inRoot()) {
_reportInvalidEOF(": expected close marker for "+_streamReadContext.typeDesc(), null);
}
}
}