JavaPropsFactory.java
package tools.jackson.dataformat.javaprop;
import java.io.*;
import java.util.*;
import tools.jackson.core.*;
import tools.jackson.core.base.TextualTSFactory;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.io.GuardedOutputStream;
import tools.jackson.core.io.IOContext;
import tools.jackson.dataformat.javaprop.impl.PropertiesBackedGenerator;
import tools.jackson.dataformat.javaprop.impl.WriterBackedGenerator;
import tools.jackson.dataformat.javaprop.io.Latin1Reader;
@SuppressWarnings("resource")
public class JavaPropsFactory
extends TextualTSFactory
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
public final static String FORMAT_NAME_JAVA_PROPERTIES = "java_properties";
protected final static String CHARSET_ID_LATIN1 = "ISO-8859-1";
final static JavaPropsSchema EMPTY_SCHEMA;
static {
EMPTY_SCHEMA = JavaPropsSchema.emptySchema();
}
/*
/**********************************************************************
/* Factory construction, configuration
/**********************************************************************
*/
public JavaPropsFactory() {
// No format-specific features yet so:
super(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(),
ErrorReportConfiguration.defaults(),
0, 0);
}
protected JavaPropsFactory(JavaPropsFactory src)
{
super(src);
}
/**
* Constructors used by {@link JavaPropsFactoryBuilder} for instantiation.
*
* @since 3.0
*/
protected JavaPropsFactory(JavaPropsFactoryBuilder b)
{
super(b);
}
@Override
public JavaPropsFactoryBuilder rebuild() {
return new JavaPropsFactoryBuilder(this);
}
/**
* Main factory method to use for constructing {@link JavaPropsFactory} instances with
* different configuration.
*/
public static JavaPropsFactoryBuilder builder() {
return new JavaPropsFactoryBuilder();
}
@Override
public JavaPropsFactory copy() {
return new JavaPropsFactory(this);
}
/**
* Instances are immutable so just return `this`
*/
@Override
public TokenStreamFactory snapshot() {
return this;
}
/*
/**********************************************************************
/* Introspection
/**********************************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
// Not positional
@Override
public boolean requiresPropertyOrdering() {
return false;
}
// Not using char[] internally
@Override
public boolean canUseCharArrays() { return false; }
@Override
public boolean canParseAsync() {
// 30-Sep-2017, tatu: No async parsing yet
return false;
}
/*
/**********************************************************************
/* Format support
/**********************************************************************
*/
@Override
public String getFormatName() {
return FORMAT_NAME_JAVA_PROPERTIES;
}
@Override
public boolean canUseSchema(FormatSchema schema) {
return schema instanceof JavaPropsSchema;
}
// No format-specific configuration, yet:
@Override
public Class<? extends FormatFeature> getFormatReadFeatureType() {
return null;
}
@Override
public Class<? extends FormatFeature> getFormatWriteFeatureType() {
return null;
}
@Override
public int getFormatReadFeatures() { return 0; }
@Override
public int getFormatWriteFeatures() { return 0; }
/*
/**********************************************************************
/* Extended parser/generator factory methods
/**********************************************************************
*/
/**
* Convenience method to allow feeding a pre-parsed {@link Properties}
* (or, generally {@link java.util.Map}) instance as input.
*/
public JavaPropsParser createParser(ObjectReadContext readCtxt,
JavaPropsSchema schema, Map<?,?> content) {
return new JavaPropsParser(readCtxt,
_createContext(_createContentReference(content), true),
readCtxt.getStreamReadFeatures(_streamReadFeatures),
schema, content, content);
}
/**
* Convenience method to allow using a pre-constructed {@link Map}
* instance as output target, so that serialized property values
* are added.
*/
public JavaPropsGenerator createGenerator(ObjectWriteContext writeCtxt,
JavaPropsSchema schema, Map<?,?> target)
{
if (schema == null) {
schema = EMPTY_SCHEMA;
}
return new PropertiesBackedGenerator(writeCtxt,
_createContext(_createContentReference(target), true),
writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
schema, target);
}
/*
/**********************************************************************
/* Overridden internal factory methods, parser
/**********************************************************************
*/
/* // fine as-is:
@Override
protected IOContext _createContext(Object srcRef, boolean resourceManaged) {
return super._createContext(srcRef, resourceManaged);
}
*/
@Override
protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
InputStream in)
{
final int stdFeatures = readCtxt.getStreamReadFeatures(_streamReadFeatures);
Properties props = _loadProperties(in, ioCtxt, stdFeatures);
return new JavaPropsParser(readCtxt, ioCtxt, stdFeatures,
_getSchema(readCtxt),
in, props);
}
@Override
protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
Reader r) {
final int stdFeatures = readCtxt.getStreamReadFeatures(_streamReadFeatures);
Properties props = _loadProperties(r, ioCtxt, stdFeatures);
return new JavaPropsParser(readCtxt, ioCtxt, stdFeatures,
_getSchema(readCtxt),
r, props);
}
@Override
protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
char[] data, int offset, int len,
boolean recyclable)
{
return _createParser(readCtxt, ioCtxt, new CharArrayReader(data, offset, len));
}
@Override
protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
byte[] data, int offset, int len)
{
return _createParser(readCtxt, ioCtxt, new Latin1Reader(data, offset, len));
}
@Override
protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt,
DataInput input) {
return _unsupported();
}
private final JavaPropsSchema _getSchema(ObjectReadContext readCtxt) {
FormatSchema sch = readCtxt.getSchema();
if (sch == null) {
return JavaPropsParser.DEFAULT_SCHEMA;
}
return (JavaPropsSchema) sch;
}
/*
/**********************************************************************
/* Overridden internal factory methods, generator
/**********************************************************************
*/
@Override
protected JsonGenerator _createGenerator(ObjectWriteContext writeCtxt,
IOContext ioCtxt, Writer out)
{
return new WriterBackedGenerator(writeCtxt, ioCtxt,
writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
_getSchema(writeCtxt),
out);
}
@Override
protected JsonGenerator _createUTF8Generator(ObjectWriteContext writeCtxt,
IOContext ioCtxt, OutputStream out)
{
// [dataformats-text#734]: Writer is constructed (and hence owned) by us, so the
// generator must always flush and close it to get its buffered content into the
// stream. It is shielded from the caller's stream, which the generator itself
// flushes or closes as per `FLUSH_PASSED_TO_STREAM` and `AUTO_CLOSE_TARGET`
// (as enabled at that point).
return new WriterBackedGenerator(writeCtxt, ioCtxt,
writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
_getSchema(writeCtxt),
_createWriter(ioCtxt, new GuardedOutputStream(out), null), out);
}
@Override
protected Writer _createWriter(IOContext ctioCtxtxt, OutputStream out, JsonEncoding enc)
{
// 27-Jan-2016, tatu: Properties javadoc is quite clear on Latin-1 (ISO-8859-1) being
// the default, so let's actually override
try {
return new OutputStreamWriter(out, CHARSET_ID_LATIN1);
} catch (IOException e) {
throw _wrapIOFailure(e);
}
}
private final JavaPropsSchema _getSchema(ObjectWriteContext ctxt) {
FormatSchema sch = ctxt.getSchema();
if (sch == null) {
return JavaPropsParser.DEFAULT_SCHEMA;
}
return (JavaPropsSchema) sch;
}
/*
/**********************************************************************
/* Low-level methods for reading/writing Properties; currently
/* we simply delegate to `java.util.Properties`
/**********************************************************************
*/
protected Properties _loadProperties(InputStream in, IOContext ctxt,
int streamReadFeatures)
{
// NOTE: Properties default to ISO-8859-1 (aka Latin-1), NOT UTF-8; this
// as per JDK documentation
// Reader is constructed (and hence owned) by us, so it must always be closed to
// have its read buffer recycled.
// [dataformats-text#720]: a source Jackson opened (File/Path) is closed by the
// base factory if construction fails, so the reader must not close it too; we
// close it only after a successful load. Caller's stream is closed along with
// the reader only if auto-closing is enabled.
// [dataformats-text#738]: `Properties.load()` reads input directly, so
// to enforce max document length we need to count what it reads
final boolean managed = ctxt.isResourceManaged();
final boolean closeStreamWithReader = !managed
&& StreamReadFeature.AUTO_CLOSE_SOURCE.enabledIn(streamReadFeatures);
Properties props = _readProperties(_constrainedReader(ctxt,
new Latin1Reader(ctxt, in, closeStreamWithReader)),
true);
if (managed) {
try {
in.close();
} catch (IOException e) {
throw _wrapIOFailure(e);
}
}
return props;
}
protected Properties _loadProperties(Reader r0, IOContext ctxt,
int streamReadFeatures)
{
// Reader is the caller's, so only close it if auto-closing is enabled
// [dataformats-text#738]: count what `Properties.load()` reads, to
// enforce max document length
return _readProperties(_constrainedReader(ctxt, r0),
_autoCloseSource(ctxt, streamReadFeatures));
}
private Properties _readProperties(Reader r0, boolean closeReader)
{
Properties props = new Properties();
// May or may not want to close the reader, so...
try {
if (closeReader) {
try (Reader r = r0) {
props.load(r);
}
} else {
props.load(r0);
}
} catch (IllegalArgumentException e) {
_reportReadException("Invalid content, problem: "+e.getMessage(), e);
} catch (IOException e) {
throw _wrapIOFailure(e);
}
return props;
}
private boolean _autoCloseSource(IOContext ctxt, int streamReadFeatures) {
return ctxt.isResourceManaged()
|| StreamReadFeature.AUTO_CLOSE_SOURCE.enabledIn(streamReadFeatures);
}
protected <T> T _reportReadException(String msg, Exception rootCause)
{
throw new StreamReadException((JsonParser) null, msg, rootCause);
}
/**
* Helper method for [dataformats-text#738]: {@link java.util.Properties#load(Reader)}
* reads input directly from the {@link Reader} given, so to enforce maximum
* document length we need to count what it reads. No wrapping (and no
* overhead) if no maximum document length configured.
*
* @since 3.1.7
*/
protected static Reader _constrainedReader(IOContext ioCtxt, Reader reader) {
StreamReadConstraints constraints = ioCtxt.streamReadConstraints();
if (constraints.hasMaxDocumentLength()) {
return new ReadConstrainedReader(reader, constraints);
}
return reader;
}
/*
/**********************************************************************
/* Helper classes
/**********************************************************************
*/
/**
* {@link Reader} decorator that enforces
* {@link StreamReadConstraints#getMaxDocumentLength()} by counting characters
* read through it. Needed since actual decoding is done by
* {@link java.util.Properties#load(Reader)}, reading content directly from
* a {@link Reader}, so the parser itself does not see input as it is consumed.
*<p>
* Only installed when constraints define a maximum document length
* (see {@link StreamReadConstraints#hasMaxDocumentLength()}).
*<p>
* NOTE: unlike in 2.x, {@link StreamConstraintsException} is unchecked in 3.x,
* so it propagates out of {@link java.util.Properties#load(Reader)} as-is
* (which only catches {@link IOException}) and needs no unwrapping by the caller.
*
* @since 3.1.7
*/
private static class ReadConstrainedReader extends Reader
{
private final Reader _delegate;
private final StreamReadConstraints _constraints;
/**
* Total number of characters read (or skipped) so far.
*/
private long _charsRead;
public ReadConstrainedReader(Reader delegate, StreamReadConstraints constraints) {
_delegate = delegate;
_constraints = constraints;
}
private void _count(long n) throws StreamConstraintsException {
if (n > 0) {
_charsRead += n;
_constraints.validateDocumentLength(_charsRead);
}
}
@Override
public int read() throws IOException {
int c = _delegate.read();
if (c >= 0) {
_count(1);
}
return c;
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int n = _delegate.read(cbuf, off, len);
_count(n);
return n;
}
@Override
public long skip(long n) throws IOException {
long skipped = _delegate.skip(n);
_count(skipped);
return skipped;
}
@Override
public boolean ready() throws IOException {
return _delegate.ready();
}
@Override
public void close() throws IOException {
_delegate.close();
}
}
}