Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow use of java.nio.file.Path as parser source, generator target (for Jackson databind#2013) #680

Merged
merged 1 commit into from
Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/main/java/com/fasterxml/jackson/core/TokenStreamFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.io.*;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;

Expand Down Expand Up @@ -703,6 +705,32 @@ public PropertyNameMatcher constructCINameMatcher(List<Named> matches, boolean a
public abstract JsonParser createParser(ObjectReadContext readCtxt,
File f) throws JacksonException;

/**
* Method for constructing parser instance to decode
* contents of specified path.
*<p>
* Encoding is auto-detected from contents according to JSON
* specification recommended mechanism. Json specification
* supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
* so auto-detection implemented only for this charsets.
* For other charsets use {@link #createParser(java.io.Reader)}.
*<p>
* Underlying input stream (needed for reading contents)
* will be <b>owned</b> (and managed, i.e. closed as need be) by
* the parser, since caller has no access to it.
*
* @param readCtxt Object read context to use
* @param p Path that contains content to parse
*
* @return Parser constructed
*
* @throws JacksonException If parser construction or initialization fails
*
* @since 3.0
*/
public abstract JsonParser createParser(ObjectReadContext readCtxt,
Path p) throws JacksonException;

/**
* Method for constructing JSON parser instance to decode
* contents of resource reference by given URL.
Expand Down Expand Up @@ -1131,6 +1159,30 @@ public abstract JsonGenerator createGenerator(ObjectWriteContext writeCtxt, File
JsonEncoding enc)
throws JacksonException;

/**
* Method for constructing generator that writes contents
* to specified path, overwriting contents it might have (or creating
* it if such path does not yet exist).
*<p>
* Underlying stream <b>is owned</b> by the generator constructed,
* i.e. generator will handle closing of path when
* {@link JsonGenerator#close} is called.
*
* @param writeCtxt Object-binding context where applicable; used for providing contextual
* configuration
* @param p Path to write contents to
* @param enc Character set encoding to use (usually {@link JsonEncoding#UTF8})
*
* @return Generator constructed
*
* @throws JacksonException If generator construction or initialization fails
*
* @since 3.0
*/
public abstract JsonGenerator createGenerator(ObjectWriteContext writeCtxt, Path p,
JsonEncoding enc)
throws JacksonException;

/**
* Method for constructing generator that writes content into specified {@link DataOutput},
* using UTF-8 encoding (with formats where encoding is user-configurable).
Expand Down Expand Up @@ -1329,6 +1381,15 @@ protected InputStream _fileInputStream(File f) throws JacksonException
}
}

protected InputStream _pathInputStream(Path p) throws JacksonException
{
try {
return Files.newInputStream(p);
} catch (IOException e) {
throw _wrapIOFailure(e);
}
}

protected OutputStream _fileOutputStream(File f) throws JacksonException
{
try {
Expand All @@ -1338,6 +1399,15 @@ protected OutputStream _fileOutputStream(File f) throws JacksonException
}
}

protected OutputStream _pathOutputStream(Path p) throws JacksonException
{
try {
return Files.newOutputStream(p);
} catch (IOException e) {
throw _wrapIOFailure(e);
}
}

protected JacksonException _wrapIOFailure(IOException e) {
return WrappedIOException.construct(e, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.*;
import java.net.URL;
import java.nio.file.Path;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.IOContext;
Expand Down Expand Up @@ -95,6 +96,16 @@ public JsonParser createParser(ObjectReadContext readCtxt,
_decorate(ioCtxt, _fileInputStream(f)));
}

@Override
public JsonParser createParser(ObjectReadContext readCtxt,
Path p) throws JacksonException
{
// true, since we create InputStream from Path
IOContext ioCtxt = _createContext(p, true);
return _createParser(readCtxt, ioCtxt,
_decorate(ioCtxt, _pathInputStream(p)));
}

@Override
public JsonParser createParser(ObjectReadContext readCtxt,
URL url) throws JacksonException
Expand Down Expand Up @@ -233,6 +244,20 @@ public JsonGenerator createGenerator(ObjectWriteContext writeCtxt,
_decorate(ioCtxt, _createWriter(ioCtxt, out, enc)));
}

@Override
public JsonGenerator createGenerator(ObjectWriteContext writeCtxt,
Path p, JsonEncoding enc)
throws JacksonException
{
final OutputStream out = _pathOutputStream(p);
final IOContext ioCtxt = _createContext(p, true, enc);
if (enc == JsonEncoding.UTF8) {
return _createUTF8Generator(writeCtxt, ioCtxt, _decorate(ioCtxt, out));
}
return _createGenerator(writeCtxt, ioCtxt,
_decorate(ioCtxt, _createWriter(ioCtxt, out, enc)));
}

/*
/**********************************************************************
/* Factory methods: abstract, for sub-classes to implement
Expand Down
157 changes: 157 additions & 0 deletions src/test/java/com/fasterxml/jackson/core/json/JsonFactoryTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.fasterxml.jackson.core.json;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.SerializedString;
Expand Down Expand Up @@ -200,4 +202,159 @@ public void testRootValues() throws Exception
g.close();
assertEquals("1/2/3", w.toString());
}

public void test_createGenerator_OutputStream() throws Exception
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JsonGenerator jsonGenerator = new JsonFactory()
.createGenerator(ObjectWriteContext.empty(), outputStream);

jsonGenerator.writeString("value");
jsonGenerator.close();

assertEquals(new String(outputStream.toByteArray(), StandardCharsets.UTF_8), "\"value\"");

// the stream has not been closed by close
outputStream.write(1);
}

public void test_createGenerator_File() throws Exception
{
Path path = Files.createTempFile("", "");
JsonGenerator jsonGenerator = new JsonFactory()
.createGenerator(ObjectWriteContext.empty(), path.toFile(), JsonEncoding.UTF8);

jsonGenerator.writeString("value");
jsonGenerator.close();

assertEquals(new String(Files.readAllBytes(path), StandardCharsets.UTF_8), "\"value\"");
}

public void test_createGenerator_Path() throws Exception
{
Path path = Files.createTempFile("", "");
JsonGenerator jsonGenerator = new JsonFactory()
.createGenerator(ObjectWriteContext.empty(), path, JsonEncoding.UTF8);

jsonGenerator.writeString("value");
jsonGenerator.close();

assertEquals(new String(Files.readAllBytes(path), StandardCharsets.UTF_8), "\"value\"");
}

public void test_createGenerator_Writer() throws Exception
{
Writer writer = new StringWriter();
JsonGenerator jsonGenerator = new JsonFactory()
.createGenerator(ObjectWriteContext.empty(), writer);

jsonGenerator.writeString("value");
jsonGenerator.close();

assertEquals(writer.toString(), "\"value\"");

// the writer has not been closed by close
writer.append('1');
}

public void test_createGenerator_DataOutput() throws Exception
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DataOutput dataOutput = new DataOutputStream(outputStream);
JsonGenerator jsonGenerator = new JsonFactory()
.createGenerator(ObjectWriteContext.empty(), dataOutput);

jsonGenerator.writeString("value");
jsonGenerator.close();

assertEquals(new String(outputStream.toByteArray(), StandardCharsets.UTF_8), "\"value\"");

// the data output has not been closed by close
dataOutput.write(1);
}

public void test_createParser_InputStream() throws Exception
{
InputStream inputStream = new ByteArrayInputStream("\"value\"".getBytes(StandardCharsets.UTF_8));
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), inputStream);

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_File() throws Exception
{
Path path = Files.createTempFile("", "");
Files.write(path, "\"value\"".getBytes(StandardCharsets.UTF_8));
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), path.toFile());

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_Path() throws Exception
{
Path path = Files.createTempFile("", "");
Files.write(path, "\"value\"".getBytes(StandardCharsets.UTF_8));
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), path);

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_Url() throws Exception
{
Path path = Files.createTempFile("", "");
Files.write(path, "\"value\"".getBytes(StandardCharsets.UTF_8));
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), path.toUri().toURL());

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_Reader() throws Exception
{
Reader reader = new StringReader("\"value\"");
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), reader);

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_ByteArray() throws Exception
{
byte[] bytes = "\"value\"".getBytes(StandardCharsets.UTF_8);
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), bytes);

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_String() throws Exception
{
String string = "\"value\"";
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), string);

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_CharArray() throws Exception
{
char[] chars = "\"value\"".toCharArray();
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), chars);

assertEquals(jsonParser.nextTextValue(), "value");
}

public void test_createParser_DataInput() throws Exception
{
InputStream inputStream = new ByteArrayInputStream("\"value\"".getBytes(StandardCharsets.UTF_8));
DataInput dataInput = new DataInputStream(inputStream);
JsonParser jsonParser = new JsonFactory()
.createParser(ObjectReadContext.empty(), dataInput);

assertEquals(jsonParser.nextTextValue(), "value");
}
}